本文主要是在 dva 做者 @sorrycc 的例子 umi + dva,完成用户管理的 CURD 应用 基础上进行的一些本身的学习记录。 关于评论区的小伙伴说的照抄 @sorrycc 大神的github文章的问题,这里回应下,没错,本文 90% 以上的文字都出自 umi + dva,完成用户管理的 CURD 应用 这篇文章,并且做者和文章出处一开始就交代清楚了,写这个文章的目的是记录一下本身跟着教程 step by step 完成这个应用的过程,并非教程,指望教程的小伙伴, @sorrycc 大大在 dva 官网上提供了不少丰富的例子,能够参考。css
2018年9月10日更新,umi 已经升级到了 2.0 版本,但愿使用 umi@2 开发的同窗请移步至 DvaJS的学习之路2 - umi@2 + dva,完成用户管理的 CURD 应用。html
先安装 dva-cli,并确保版本是 1.0.0-beta.2 或以上。node
$ cnpm i dva-cli@next -g
$ dva -v
dva-cli version 1.0.0-beta.4
dva version 2.3.1
复制代码
这里须要注意的是安装dva-cli@next版本的缘由是目前 umi 还未提供官方的脚手架工具,须要 dva + umi 结合使用可使用 dva-cli@next 方式来初始化项目。详见:例子和脚手架。react
而后建立应用:git
$ dva new user-dashboard
$ cd user-dashboard
复制代码
修改 .umirc.js
,加上 "proxy" 配置:es6
proxy: {
"/api": {
"target": "http://jsonplaceholder.typicode.com/",
"changeOrigin": true,
"pathRewrite": { "^/api" : "" }
}
},
复制代码
而后启动应用:(这个命令一直开着,后面不须要重启)github
$ npm start
复制代码
浏览器会自动开启,并打开 http://localhost:8000。npm
访问 http://localhost:8000/api/users ,就能访问到 jsonplaceholder.typicode.com/users 的数据。(因为 typicode.com 服务的稳定性,偶尔可能会失败。不过不要紧,正好便于咱们以后对于出错的处理)json
umi 中文件即路由,因此咱们要新增路由,新建文件便可,详见 umijs.org/guide/route… 。api
新建 src/pages/users/page.js
,内容以下:
export default () => {
return (
<div>
Users Page
</div>
)
}
复制代码
而后访问 http://localhost:8000/users ,你会看到 Users Page
的输出。
注意:使用 umi 约定
src/pages
目录下的文件即路由,而文件则导出 react 组件。能够看到 umi 的特色:以页面维度,将 models 、 services 组织到一块儿。
新增 service: src/pages/users/services/users.js
:
import request from '../../../utils/request';
export function fetch({ page = 1 }) {
return request(`/api/users?_page=${page}&_limit=5`);
}
复制代码
注意这里的
page
参数默认为1
,limit
参数设置为5
新增 model: src/pages/users/models/users.js
,内容以下:
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 });
}
});
},
},
};
复制代码
这里面有一些写法以前没有用过:好比
{ payload: { data: list, total } }
, 这个是析构时配 alias 的写法;return { ...state, list, total }
的写法用了Spread Operator...
来组合新对象, 详见 dva知识地图#ES6对象和数组
因为咱们须要从 response headers 中获取 total users 数量,因此须要改造下 src/utils/request.js
:
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;
}
复制代码
注意:这里使用了 ES7 的 async/await 特性,开始对这块不是很熟悉,看了一些关于 async/await 的文章,发现确实比 Promise 的写法语义化更加明显,下面这段是我改写的 Promise 写法:
/**
* 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 function request(url, options) {
let headers = {}
return fetch(url, options)
.then(checkStatus)
.then(response => {
const data = parseJSON(response)
if (response.headers.get('x-total-count')) {
headers['x-total-count'] = response.headers.get('x-total-count');
}
return data;
}).then((data) => {
return {data, headers}
}).catch(err => ({ err }));
}
复制代码
切换到浏览器(会自动刷新),应该没任何变化,由于数据虽然好了,但并无视图与之关联。可是打开 Redux 开发者工具,应该能够看到 users/fetch
和 users/save
的 action 以及相关的 state 。
咱们把组件存在 src/pages/users/components
里,因此在这里新建 Users.js
和 Users.css
。具体参考这个 Commit。
需留意两件事:
src/constants.js
改完后,切换到浏览器,应该能看到带分页的用户列表。
有几点须要注意:
- Users.js 里面使用了 antd 的组件,可是项目并无手动安装 antd, 原来是 umi 帮咱们引入了 antd 。
- Users.js 里面将model和组件链接了起来,注意
const { list, total, page } = state.users;
里面的users
为model
里面的namespace
名称。- 咱们没有手动注册 model,umi 帮咱们进行了这一步操做, 详见
src/pages/.umi/DvaContainer.js
文件,该文件会自动更新。相关规则详见 umi官网#model注册 一节。- 能够直接使用 css module
添加 layout 布局,使得咱们能够在首页和用户列表页之间来回切换。umi 里约定 layouts/index.js
为全局路由,因此咱们新增 src/layouts/index.js
和 CSS 文件便可。
参考这个 Commit。
注意:
页头的菜单会随着页面切换变化,高亮显示当前页所在的菜单项
dva 有一个管理 effects 执行的 hook,并基于此封装了 dva-loading 插件。经过这个插件,咱们能够没必要一遍遍地写 showLoading 和 hideLoading,当发起请求时,插件会自动设置数据里的 loading 状态为 true 或 false 。而后咱们在渲染 components 时绑定并根据这个数据进行渲染。
umi-plugin-dva 默认内置了 dva-loading 插件。
而后在 src/components/Users/Users.js
里绑定 loading 数据:
+ loading: state.loading.models.users,
复制代码
具体参考这个 Commit 。
刷新浏览器,你的用户列表有 loading 了没?
只改一个文件 src/pages/users/components/Users.js
就好。
处理分页有两个思路:
咱们用的是思路 2 的方式,好处是用户能够直接访问到 page 2 或其余页面。
参考这个 Commit 。
通过前面的 8 步,应用的总体脉络已经清晰,相信你们已经对总体流程也有了必定了解。
后面的功能调整基本均可以按照如下三步进行:
service
model
component 咱们如今开始增长用户删除功能。
service, 修改 src/pages/users/services/users.js
:
export function remove(id) {
return request(`/api/users/${id}`, {
method: 'DELETE',
});
}
复制代码
src/pages/users/models/users.js
:*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 } });
},
复制代码
src/pages/users/components/Users.js
,替换 deleteHandler
内容:dispatch({
type: 'users/remove',
payload: id,
});
复制代码
切换到浏览器,删除功能应该已经生效。
处理用户编辑和前面的同样,遵循三步走:
src/pages/users/services/users.js
:export function patch(id, values) {
return request(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(values),
});
}
复制代码
再是 model,修改 src/pages/users/models/users.js
:
*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 状态,有几种选择:
另外,怎么存也是个问题,能够:
UserModal
的组件。完成后,切换到浏览器,应该就能对用户进行编辑了。
相比用户编辑,用户建立更简单些,由于能够共用 UserModal 组件。和 Step 10 比较相似,就不累述了,详见 Commit 。
到这里,咱们已经完成了一个完整的 CURD 应用。若是感兴趣,能够进一步看下 dva 和 umi 的资料:
(完)
作这个练习主要了解了 dva 和 umi 搭配使用的方式,使用 umi 的一些写法和 umi 的特色。感受学习了Redux以后,dva上手会很快,dva 的网站资源很丰富,但愿和你们一块儿学习,后续还会有 dva 学习的文章。