{"action":"created","issue":{"url":"https://api.github.com/repos/sorrycc/blog/issues/62","repository_url":"https://api.github.com/repos/sorrycc/blog","labels_url":"https://api.github.com/repos/sorrycc/blog/issues/62/labels{/name}","comments_url":"https://api.github.com/repos/sorrycc/blog/issues/62/comments","events_url":"https://api.github.com/repos/sorrycc/blog/issues/62/events","html_url":"https://github.com/sorrycc/blog/issues/62","id":299281438,"number":62,"title":"umi + dva，完成用户管理的 CURD 应用","user":{"login":"sorrycc","id":35128,"avatar_url":"https://avatars1.githubusercontent.com/u/35128?v=4","gravatar_id":"","url":"https://api.github.com/users/sorrycc","html_url":"https://github.com/sorrycc","followers_url":"https://api.github.com/users/sorrycc/followers","following_url":"https://api.github.com/users/sorrycc/following{/other_user}","gists_url":"https://api.github.com/users/sorrycc/gists{/gist_id}","starred_url":"https://api.github.com/users/sorrycc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sorrycc/subscriptions","organizations_url":"https://api.github.com/users/sorrycc/orgs","repos_url":"https://api.github.com/users/sorrycc/repos","events_url":"https://api.github.com/users/sorrycc/events{/privacy}","received_events_url":"https://api.github.com/users/sorrycc/received_events","type":"User","site_admin":false},"labels":[{"id":844848110,"url":"https://api.github.com/repos/sorrycc/blog/labels/Umi","name":"Umi","color":"c5def5","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":51,"created_at":"2018-02-22T09:39:20Z","updated_at":"2018-04-13T10:40:58Z","closed_at":null,"author_association":"OWNER","body":"\n本文是 [sorrycc/blog#18](https://github.com/sorrycc/blog/issues/18) 的更新版，基于 dva@2、[umi](https://github.com/umijs/umi) 和 [umi-plugin-dva](https://github.com/umijs/umi/tree/master/packages/umi-plugin-dva)。下面会一步步引导大家如何创建一个 CURD 应用，包含查询、编辑、删除、创建，以及分页处理，数据 mock，自动处理 loading 状态等。\n\n全部代码在 [umijs/umi-dva-user-dashboard](https://github.com/umijs/umi-dva-user-dashboard)，对 dva 熟练的同学可以看代码。\n\n最终效果：\n\n![](https://zos.alipayobjects.com/rmsportal/aASJbDdBkhmtxxdbtLCe.gif)\n\n## 开始之前：\n\n* 确保 node 版本是 8.4 或以上\n* 用 [cnpm](https://github.com/cnpm/cnpm) 或 [yarn](https://github.com/yarnpkg/yarn) 能节约你安装依赖的时间\n\n## Step 1. 安装 [dva-cli](https://github.com/dvajs/dva-cli) 并创建应用\n\n先安装 dva-cli，**并确保版本是 1.0.0-beta.2 或以上**。\n\n```bash\n$ npm i dva-cli@next -g\n$ dva -v\n1.0.0-beta.2\n```\n\n然后创建应用：\n\n```bash\n$ dva new user-dashboard\n$ cd user-dashboard\n```\n\n## Step 2. 配置代理，能通过 RESTFul 的方式访问 http://localhost:8000/api/users\n\n修改 `.webpackrc.js`，加上 `\"proxy\"` 配置：\n\n```json\n\"proxy\": {\n  \"/api\": {\n    \"target\": \"http://jsonplaceholder.typicode.com/\",\n    \"changeOrigin\": true,\n    \"pathRewrite\": { \"^/api\" : \"\" }\n  }\n},\n```\n\n然后启动应用：(这个命令一直开着，后面不需要重启)\n\n```bash\n$ npm start\n```\n\n浏览器会自动开启，并打开 [http://localhost:8000](http://localhost:8000)。\n\n<img src=\"https://gw.alipayobjects.com/zos/rmsportal/NTLpZIgweZNLfKTCcJsX.png?t=212\" width=\"400\" height=\"362\" />\n\n访问 [http://localhost:8000/api/users](http://localhost:8000/api/users) ，就能访问到 http://jsonplaceholder.typicode.com/users 的数据。(由于 typicode.com 服务的稳定性，偶尔可能会失败。不过没关系，正好便于我们之后对于出错的处理)\n\n<img src=\"https://zos.alipayobjects.com/rmsportal/oFAPJkxcIYeZFjYATuUL.png\" width=\"437\" height=\"286\" />\n\n## Step 3. 生成 users 路由\n\numi 中文件即路由，所以我们要新增路由，新建文件即可，详见 https://umijs.org/docs/zh-Hans/router.html 。\n\n新建 `src/pages/users/page.js`，内容如下：\n\n```js\nexport default () => {\n  return (\n    <div>\n      Users Page\n    </div>\n  )\n}\n```\n\n然后访问 [http://localhost:8000/users](http://localhost:8000/users)，你会看到 `Users Page` 的输出。\n\n## Step 4. 构造 users model 和 service\n\n新增 `src/pages/users/models/users.js`，内容如下：\n\n```js\nimport * as usersService from '../services/users';\n\nexport default {\n  namespace: 'users',\n  state: {\n    list: [],\n    total: null,\n  },\n  reducers: {\n    save(state, { payload: { data: list, total } }) {\n      return { ...state, list, total };\n    },\n  },\n  effects: {\n    *fetch({ payload: { page } }, { call, put }) {\n      const { data, headers } = yield call(usersService.fetch, { page });\n      yield put({ type: 'save', payload: { data, total: headers['x-total-count'] } });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/users') {\n          dispatch({ type: 'fetch', payload: query });\n        }\n      });\n    },\n  },\n};\n```\n\n新增 `src/pages/users/services/users.js`：\n\n```js\nimport request from '../../../utils/request';\n\nexport function fetch({ page = 1 }) {\n  return request(`/api/users?_page=${page}&_limit=5`);\n}\n```\n\n由于我们需要从 response headers 中获取 total users 数量，所以需要改造下 `src/utils/request.js`：\n\n```js\nimport fetch from 'dva/fetch';\n\nfunction checkStatus(response) {\n  if (response.status >= 200 && response.status < 300) {\n    return response;\n  }\n\n  const error = new Error(response.statusText);\n  error.response = response;\n  throw error;\n}\n\n/**\n * Requests a URL, returning a promise.\n *\n * @param  {string} url       The URL we want to request\n * @param  {object} [options] The options we want to pass to \"fetch\"\n * @return {object}           An object containing either \"data\" or \"err\"\n */\nexport default async function request(url, options) {\n  const response = await fetch(url, options);\n\n  checkStatus(response);\n\n  const data = await response.json();\n\n  const ret = {\n    data,\n    headers: {},\n  };\n\n  if (response.headers.get('x-total-count')) {\n    ret.headers['x-total-count'] = response.headers.get('x-total-count');\n  }\n\n  return ret;\n}\n```\n\n切换到浏览器（会自动刷新），应该没任何变化，因为数据虽然好了，但并没有视图与之关联。但是打开 Redux 开发者工具，应该可以看到 `users/fetch` 和 `users/save` 的 action 以及相关的 state 。\n\n![](https://zos.alipayobjects.com/rmsportal/DtNoXjQAdMSJqzOSEZAo.png)\n\n## Step 5. 添加界面，让用户列表展现出来\n\n我们把组件存在 src/pages/users/components 里，所以在这里新建 Users.js 和 Users.css。具体参考这个 [Commit](https://github.com/umijs/umi-dva-user-dashboard/commit/ee9ccba33736c31dd8271fabf925c58a9edf76d0)。\n\n需留意两件事：\n\n1. 对 model 进行了微调，加入了 page 表示当前页\n2. 由于 components 和 services 中都用到了 pageSize，所以提取到 `src/constants.js`\n\n改完后，切换到浏览器，应该能看到带分页的用户列表。\n\n![](https://zos.alipayobjects.com/rmsportal/gcElpRpTDkpUEmrXRGHn.png)\n\n## Step 6. 添加 layout\n\n添加 layout 布局，使得我们可以在首页和用户列表页之间来回切换。umi 里约定 layouts/index.js 为全局路由，所以我们新增 `src/layouts/index.js` 和 CSS 文件即可。\n\n参考这个 [Commit](https://github.com/umijs/umi-dva-user-dashboard/commit/2c763479762ed9fb16e151d362df2b8d68631cd0)。\n\n注意：\n\n1. 页头的菜单会随着页面切换变化，高亮显示当前页所在的菜单项\n\n## Step 7. 处理 loading 状态\n\ndva 有一个管理 effects 执行的 hook，并基于此封装了 dva-loading 插件。通过这个插件，我们可以不必一遍遍地写 showLoading 和 hideLoading，当发起请求时，插件会自动设置数据里的 loading 状态为 true 或 false 。然后我们在渲染 components 时绑定并根据这个数据进行渲染。\n\numi-plugin-dva 默认内置了 dva-loading 插件。\n\n然后在 `src/components/Users/Users.js` 里绑定 loading 数据：\n\n```diff\n+ loading: state.loading.models.users,\n```\n\n具体参考这个 [Commit](https://github.com/umijs/umi-dva-user-dashboard/commit/f81d36c639d35e67db8b2d0a65b561e4af203eb1) 。\n\n刷新浏览器，你的用户列表有 loading 了没? \n\n## Step 8. 处理分页\n\n只改一个文件 `src/pages/users/components/Users.js` 就好。\n\n处理分页有两个思路：\n\n1. 发 action，请求新的分页数据，保存到 model，然后自动更新页面\n2. 切换路由 (由于之前监听了路由变化，所以后续的事情会自动处理)\n\n我们用的是思路 2 的方式，好处是用户可以直接访问到 page 2 或其他页面。\n\n参考这个 [Commit](https://github.com/umijs/umi-dva-user-dashboard/commit/156220ce72e7d26e2823e8b98fbbd7e5c991db9b) 。\n\n## Step 9. 处理用户删除\n\n经过前面的 8 步，应用的整体脉络已经清晰，相信大家已经对整体流程也有了一定了解。\n\n后面的功能调整基本都可以按照以下三步进行：\n\n1. service\n2. model\n3. component\n\n我们现在开始增加用户删除功能。\n\n1. service, 修改 `src/pages/users/services/users.js`：\n\n```js\nexport function remove(id) {\n  return request(`/api/users/${id}`, {\n    method: 'DELETE',\n  });\n}\n```\n\n2. model, 修改 `src/pages/users/models/users.js`：\n\n```js\n*remove({ payload: id }, { call, put, select }) {\n  yield call(usersService.remove, id);\n  const page = yield select(state => state.users.page);\n  yield put({ type: 'fetch', payload: { page } });\n},\n```\n\n3. component, 修改 `src/pages/users/components/Users.js`，替换 `deleteHandler` 内容：\n\n```js\ndispatch({\n  type: 'users/remove',\n  payload: id,\n});\n```\n\n切换到浏览器，删除功能应该已经生效。\n\n## Step 10. 处理用户编辑\n\n处理用户编辑和前面的一样，遵循三步走：\n\n1. service\n2. model\n3. component\n\n先是 service，修改 `src/pages/users/services/users.js`：\n\n```js\nexport function patch(id, values) {\n  return request(`/api/users/${id}`, {\n    method: 'PATCH',\n    body: JSON.stringify(values),\n  });\n}\n```\n\n再是 model，修改 `src/pages/users/models/users.js`：\n\n```js\n*patch({ payload: { id, values } }, { call, put, select }) {\n  yield call(usersService.patch, id, values);\n  const page = yield select(state => state.users.page);\n  yield put({ type: 'fetch', payload: { page } });\n},\n```\n\n最后是 component，详见 [Commit](https://github.com/umijs/umi-dva-user-dashboard/commit/6044e2fcaf53ce3645217194abe0fffa7585477c)。\n\n需要注意的一点是，我们在这里如何处理 Modal 的 visible 状态，有几种选择：\n\n1. 存 dva 的 model state 里\n2. 存 component state 里\n\n另外，怎么存也是个问题，可以：\n\n1. 只有一个 visible，然后根据用户点选的 user 填不同的表单数据\n2. 几个 user 几个 visible\n\n此教程选的方案是 2-2，即存 component state，并且 visible 按 user 存。另外为了使用的简便，封装了一个 `UserModal` 的组件。\n\n完成后，切换到浏览器，应该就能对用户进行编辑了。\n\n## Step 11. 处理用户创建\n\n相比用户编辑，用户创建更简单些，因为可以共用 `UserModal` 组件。和 Step 10 比较类似，就不累述了，详见 [Commit](https://github.com/umijs/umi-dva-user-dashboard/commit/91708ca1e39234a4dc07aa7a7193d05358894329) 。\n\n---\n\n到这里，我们已经完成了一个完整的 CURD 应用。如果感兴趣，可以进一步看下 dva 和 umi 的资料：\n\n* [dva 官网](https://github.com/dvajs/dva)\n* [umi 官网](https://umijs.org/)\n\n（完）\n\n"},"comment":{"url":"https://api.github.com/repos/sorrycc/blog/issues/comments/381097335","html_url":"https://github.com/sorrycc/blog/issues/62#issuecomment-381097335","issue_url":"https://api.github.com/repos/sorrycc/blog/issues/62","id":381097335,"user":{"login":"yautah","id":7170,"avatar_url":"https://avatars0.githubusercontent.com/u/7170?v=4","gravatar_id":"","url":"https://api.github.com/users/yautah","html_url":"https://github.com/yautah","followers_url":"https://api.github.com/users/yautah/followers","following_url":"https://api.github.com/users/yautah/following{/other_user}","gists_url":"https://api.github.com/users/yautah/gists{/gist_id}","starred_url":"https://api.github.com/users/yautah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yautah/subscriptions","organizations_url":"https://api.github.com/users/yautah/orgs","repos_url":"https://api.github.com/users/yautah/repos","events_url":"https://api.github.com/users/yautah/events{/privacy}","received_events_url":"https://api.github.com/users/yautah/received_events","type":"User","site_admin":false},"created_at":"2018-04-13T10:40:58Z","updated_at":"2018-04-13T10:40:58Z","author_association":"NONE","body":"接上面的\r\n因为以前一直处理token 的auth都是hoc的方式。\r\n突然发现umi这种方式没什么思路了。。。"}}
{"actor":{"display_login":"yautah"}}