JobbyM's Blog

12步30分钟 完成用户管理的CURD应用

原文链接12步20分钟,完成用户管理的CURD 应用(react+dva+antd),本文对原文进行了部分修改。

开始为了学习mobile-ant,但是发现推荐使用dva,因此找到了这篇文章。

本文是按照12步20分钟,完成用户管理的CURD 应用(react+dva+antd) 一步一步创建一个CURD 应用,包含查询、编辑、删除、创建,以及分页处理,数据mock,自动处理loading 状态等,基于react、dva 和antd。

可以通过如下方法进行学习:

  1. clone 本项目
    1
    git clone https://github.com/JobbyM/dva-example-user-dashboard.git
  2. 进入项目
    1
    cd dva-example-user-dashboard
  3. 安装依赖
    1
    npm install
  4. 启动
    1
    npm start
  5. 从Step 2 到Step 12 都添加了tag,所以可以通过如下方法进行学习每一步
    1
    git checkout v2.0.0
    之后,运行
    1
    npm start
    就可以看到Step 2的效果了,不过Step 2 需要在浏览器中输入http://localhost:8000/#/users 才可以看到响应的效果。

最终效果:

开始之前

  1. node 版本要求为6.5+,本地为6.10.2

Step 1. 安装dva-cli 并创建应用

本地安装版本为0.7.8

1
2
3
npm i dva-cli -g
dva -v
0.7.8

然后创建应用:

1
2
dva new dva-example-user-dashboard
cd dva-example-user-dashboard

Step 2. 配置antd 和babel-plugin-import

babel-plugin-import 用于按需引入antd 的JavaScript 和CSS,这样打包出来的文件不至于太大。

1
2
npm i antd --save
npm i babel-plugin-import --save-dev

修改.roadhogrc,在"extraBabelPlugins" 里加上:

1
["import", { "libraryName": "antd", "style": "css" }]

注意,这里的修改是在.roadhogrc 中的"env" 中的"development" 中的"extraBabelPlugins" 中进行修改的

Step 3. 配置大力,能通过RESTFul 的方式访问 http://localhost:8000/api/users

修改.roadhogrc,加上"proxy" 配置:

1
2
3
4
5
6
7
"proxy": {
"/api": {
"target": "http://jsonplaceholder.typicode.com/",
"changeOrigin": true,
"pathRewrite": { "^/api" : "" }
}
},

注意,这里的修改是在.roadhogrc 中的"env" 中的"development" 中进行添加的

然后启动应用:(这个命令一直开着,后面不需要重启)

1
npm start

浏览器会自动启动,并打开http://localhost:8000

访问http://localhost:8000/api/users, 就能访问到http://jsonplaceholder.typicode.com/users 的数据。(由于typicode.com 服务的稳定性,偶尔可能会失败。不过没关系,正好便于我们之后对于出错的处理)

Step 4. 生成users 路由

用 dva-cli 生成路由

1
dva g route users

然后访问http://localhost:8000/#/users

Step 5. 构造users model 和service

用dva-cli 生成Model:

1
dva g model users

修改src/models/users.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import * as usersService from '../services/users';

export default {
namespace: 'users',
state: {
list: [],
total: null,
},
reducers: {
save(state, { payload: { data: list, total } }) {
return { ...state, list, total }
},
},
effects: {
*fetch({ payload: { page } }, { call, put }){
const { data, headers } = yield call(usersService.fetch, { page })
yield put({ type: 'save', payload: { data, total: headers['x-total-count'] } })
},
},
subscriptions: {
setup({ dispatch, history }){
return history.listen(({pathname, query}) => {
if(pathname === '/users'){
dispatch({ type: 'fetch', payload: query })
}
});
},
},
};

新增src/services/users.js

1
2
3
4
5
import request from '../utils/request';

export function fetch({ page = 1 }) {
return request(`/api/users?_page=${page}&_limit=5`)
}

由于我们需要从respose headers 中获取total users 数量,所以需要改造下 src/utils/request.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import fetch from 'dva/fetch';

function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}

const error = new Error(response.statusText);
error.response = response;
throw error;
}

/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default async function request(url, options) {
const response = await fetch(url, options)

checkStatus(response)

const data = await response.json()

const ret = {
data,
headers: {},
}

if(response.headers.get('x-total-count')){
ret.headers['x-total-count'] = response.headers.get('x-total-count')
}

return ret;
}

切换到浏览器(会自动刷新),应该没有任何变化,因为数据虽然好了,但并没有视图与之关联。但是打开Redux 开发者工具,应该可以看到users/fetchusers/save 的action 以及相关的state。

Step 6. 添加界面,让用户列表展现出来

用dva-cli 生成component;

1
dva g component Users/Users

然后修改生成出来的src/components/Users/Users.jssrc/components/Users/Users.css,并在src/route/Users.js 中引用他。具体参考这个Commit
需留意两件事:

  1. 对model 进行了微调,加入了page 表示当前页
  2. 由于components 和services 中都用到了pageSize,所以提取到了src/constants.js

改完后,切换浏览器,应该能看到带分页的用户列表。

Step 7. 添加layout

添加layout 布局,使得我们可以在首页和用户列表页之间来回切换。

  1. 添加布局,src/components/MainLayout.js 和CSS 文件
  2. src/routes 文件夹下的文件中引用这个布局

参考这个Commit

注意:

  1. 页头的菜单会随着页面切换变化,高亮显示当前页所在的菜单项

Step 8. 通过dva-loading 处理loading 状态

dva 有一个管理effects 执行的hook,并基于此封装了dva-loading 插件。通过这个插件,我们可以不必一遍一遍写showLoading 和hideLoading,当发起请求时,插件会自动设置数据里的loading 状态为true 或false。然后我们在渲染components 时绑定并根据这个数据进行渲染。

先安装dva-loading:

1
npm i dva-loading --save

修改src/index.js 加载插件,在合适的地方加入下面两句:

1
2
import createLoading from 'dva-loading';
app.use(createLoading())

然后在src/components/Users/Users.js 里绑定loading 数据:

1
loading: state.loading.models.users,

具体参考这个Commit

切换浏览器,你的用户列表有loading 了没?

Step 9. 处理分页

只改一个文件src/components/Users/Users.js 就好。

处理分页有两个思路:

  1. 发起action,请求新的分页数据,保存到model,然后自动更新
  2. 切换路由(由于之前监听了路由变化,所以后续的事情会自动处理)

我们用的是思路2 的方式,好处是用户可以直接访问到page 2 或其他页面。

参考这个Commit

Step 10. 处理用户删除

经过前面的9 步,应用的整体脉络已经清晰,相信大家已经对整体流程也有了一定了解。

后面的功能调整都可以按照以下三步进行:

  1. services
  2. model
  3. component

我们现在开始增加用户删除功能。

  1. service,修改src/services/users.js

    1
    2
    3
    4
    5
    export function remove(id) {
    return request(`/api/users/${id}`, {
    method: 'DELETE',
    });
    }
  2. model,修改src/models/users.js

    1
    2
    3
    4
    5
    *remove({ payload: id }, { call, put, select }) {
    yield call(usersService.remove, id);
    const page = yield select(state => state.users.page);
    yield put({ type: 'fetch', payload: { page } });
    },
  3. component,修改src/components/Users.js,替换deleteHandler 内容:

    1
    2
    3
    4
    dispatch({
    type: 'users/remove',
    payload: id,
    });

Step 11. 处理用户编辑

处理用户编辑和前面的一样,遵循三步走:

  1. services
  2. model
  3. component

先是service,修改src/services/users.js

1
2
3
4
5
6
export function patch(id, values) {
return request(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(values),
});
}

再是model,修改src/models/users.js

1
2
3
4
5
*patch({ payload: { id, values } }, { call, put, select }) {
yield call(usersService.patch, id, values);
const page = yield select(state => state.users.page);
yield put({ type: 'fetch', payload: { page } });
},

最后是component,详见Commit

需要注意的一点是,我们在这里如何处理Modal 的visible 状态,有几种选择:

  1. 存dva 的model state 里
  2. 存component state 里

另外,怎么存也是个问题,可以:

  1. 只有一个visible,然后根据用户点选的user 填不同的表单数据
  2. 几个user 几个visible

此教程选的方案是202,即存component state,并且visible 按user 存。另外为了使用的简便,封装了一个UserModal 的组件。

完成后,切换到浏览器,应该就能够对用户进行编辑了。

Step 12. 处理用户创建

先比用户编辑,用户创建更简单些,因为可以共用UserModal 组件。和Step 11 比较类似,就不赘述了,详见Commit

到这里,我们已经完成了一个完整的CURD 应用。但仅仅是完成,并不完善,比如:

  • 如何处理错误,比如请求等
  • 如何处理请求超时
  • 如果根据路由动态加载JS 和CSS

请期待下一篇。