本文的目的是介绍用户管理的CURD应用(react+dva+antd)的详细情况,特别关注用户管理的三种功能的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解用户管理的
本文的目的是介绍用户管理的 CURD 应用 (react+dva+antd)的详细情况,特别关注用户管理的三种功能的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解用户管理的 CURD 应用 (react+dva+antd)的机会,同时也不会遗漏关于Ant Design Pro(React/dva/antd)、create-react-app 引入 antd 及 解决 antd 样式无法显示的bug、create-react-app+antd+react-css-modules配置、create-react-app同时对多个框架(antd+antd-mobile)做按需加载的方法的知识。
本文目录一览:- 用户管理的 CURD 应用 (react+dva+antd)(用户管理的三种功能)
- Ant Design Pro(React/dva/antd)
- create-react-app 引入 antd 及 解决 antd 样式无法显示的bug
- create-react-app+antd+react-css-modules配置
- create-react-app同时对多个框架(antd+antd-mobile)做按需加载的方法
用户管理的 CURD 应用 (react+dva+antd)(用户管理的三种功能)
用户管理的 CURD 应用
本文会一步步引导大家如何创建一个 CURD 应用,包含查询、编辑、删除、创建,以及分页处理,数据 mock,自动处理 loading 状态等,基于 react, dva 和 antd 。
最终效果:
开始之前:
- 确保 node 版本是 6.5 +
- 用 cnpm 或 yarn 能节约你安装依赖的时间
Step 1. 安装 dva-cli 并创建应用
先安装 dva-cli,并确保版本是 0.7 或以上。
$ npm i dva-cli -g $ dva -v 0.7.0
然后创建应用:
$ dva new user-dashboard $ cd user-dashboard
Step 2. 配置 antd 和 babel-plugin-import
babel-plugin-import 用于按需引入 antd 的 JavaScript 和 CSS,这样打包出来的文件不至于太大。
$ npm i antd --save $ npm i babel-plugin-import --save-dev
修改 .roadhogrc,在 "extraBabelPlugins" 里加上:
["import",{ "libraryName": "antd","style": "css" }]
Step 3. 配置代理,能通过 RESTFul 的方式访问 http://localhost:8000/api/users
修改 .roadhogrc,加上 "proxy" 配置:
"proxy": { "/api": { "target": "http://jsonplaceholder.typicode.com/","changeOrigin": true,"pathRewrite": { "^/api" : "" } } },
然后启动应用:(这个命令一直开着,后面不需要重启)
$ npm start
浏览器会自动开启,并打开 http://localhost:8000 。
访问 http://localhost:8000/api/users ,就能访问到 http://jsonplaceholder.typicode.com/users 的数据。(由于 typicode.com 服务的稳定性,偶尔可能会失败。不过没关系,正好便于我们之后对于出错的处理)
Step 4. 生成 users 路由
用 dva-cli 生成路由:
$ dva g route users
然后访问 http://localhost:8000/#/users 。
Step 5. 构造 users model 和 service
用 dva-cli 生成 Model :
$ dva g model users
修改 src/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 }); } }); },};
新增 src/services/users.js:
import request from '../utils/request'; export function fetch({ page = 1 }) { return request(`/api/users?_page=${page}&_limit=5`); }
由于我们需要从 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; }
切换到浏览器(会自动刷新),应该没任何变化,因为数据虽然好了,但并没有视图与之关联。但是打开 Redux 开发者工具,应该可以看到 users/fetch 和 users/save 的 action 以及相关的 state 。
Step 6. 添加界面,让用户列表展现出来
用 dva-cli 生成 component:
$ dva g component Users/Users
然后修改生成出来的 src/components/Users/Users.js 和 src/components/Users/Users.css,并在 src/routes/Users.js 中引用他。具体参考这个 Commit。
需留意两件事:
- 对 model 进行了微调,加入了 page 表示当前页
- 由于 components 和 services 中都用到了 pageSize,所以提取到 src/constants.js
改完后,切换到浏览器,应该能看到带分页的用户列表。
Step 7. 添加 layout
添加 layout 布局,使得我们可以在首页和用户列表页之间来回切换。
- 添加布局,src/components/MainLayout/MainLayout.js 和 CSS 文件
- 在 src/routes 文件夹下的文件中引用这个布局
参考这个 Commit。
注意:
- 页头的菜单会随着页面切换变化,高亮显示当前页所在的菜单项
Step 8. 通过 dva-loading 处理 loading 状态
dva 有一个管理 effects 执行的 hook,并基于此封装了 dva-loading 插件。通过这个插件,我们可以不必一遍遍地写 showLoading 和 hideLoading,当发起请求时,插件会自动设置数据里的 loading 状态为 true 或 false 。然后我们在渲染 components 时绑定并根据这个数据进行渲染。
先安装 dva-loading :
$ npm i dva-loading --save
修改 src/index.js 加载插件,在合适的地方加入下面两句:
+ import createLoading from 'dva-loading'; + app.use(createLoading());
然后在 src/components/Users/Users.js 里绑定 loading 数据:
+ loading: state.loading.models.users,
具体参考这个 Commit 。
切换到浏览器,你的用户列表有 loading 了没?
Step 9. 处理分页
只改一个文件 src/components/Users/Users.js 就好。
处理分页有两个思路:
- 发 action,请求新的分页数据,保存到 model,然后自动更新页面
- 切换路由 (由于之前监听了路由变化,所以后续的事情会自动处理)
我们用的是思路 2 的方式,好处是用户可以直接访问到 page 2 或其他页面。
参考这个 Commit 。
Step 10. 处理用户删除
经过前面的 9 步,应用的整体脉络已经清晰,相信大家已经对整体流程也有了一定了解。
后面的功能调整基本都可以按照以下三步进行:
- service
- model
- component
我们现在开始增加用户删除功能。
- service,修改 src/services/users.js:
export function remove(id) { return request(`/api/users/${id}`,{ method: 'DELETE',}); }
- model,修改 src/models/users.js:
*remove({ payload: id },put,select }) { yield call(useRSService.remove,id); const page = yield select(state => state.users.page); yield put({ type: 'fetch',payload: { page } }); },
- component,修改 src/components/Users/Users.js,替换 deleteHandler 内容:
dispatch({ type: 'users/remove',payload: id,});
切换到浏览器,删除功能应该已经生效。
Step 11. 处理用户编辑
处理用户编辑和前面的一样,遵循三步走:
- service
- model
- component
先是 service,修改 src/services/users.js:
export function patch(id,values) { return request(`/api/users/${id}`,{ method: 'PATCH',body: JSON.stringify(values),}); }
再是 model,修改 src/models/users.js:
*patch({ payload: { id,values } },select }) { yield call(useRSService.patch,id,values); const page = yield select(state => state.users.page); yield put({ type: 'fetch',
最后是 component,详见 Commit。
需要注意的一点是,我们在这里如何处理 Model 的 visible 状态,有几种选择:
- 存 dva 的 model state 里
- 存 component state 里
另外,怎么存也是个问题,可以:
- 只有一个 visible,然后根据用户点选的 user 填不同的表单数据
- 几个 user 几个 visible
此教程选的方案是 2-2,即存 component state,并且 visible 按 user 存。另外为了使用的简便,封装了一个 UserModal 的组件。
完成后,切换到浏览器,应该就能对用户进行编辑了。
Step 12. 处理用户创建
相比用户编辑,用户创建更简单些,因为可以共用 UserModal 组件。和 Step 11 比较类似,就不累述了,详见 Commit 。
到这里,我们已经完成了一个完整的 CURD 应用。但仅仅是完成,并不完善,比如:
- 如何处理错误,比如请求等
- 如何处理请求超时
- 如何根据路由动态加载 JS 和 CSS
- ...
请期待下一篇。
Ant Design Pro(React/dva/antd)
Ant Design Pro 是一个企业级中后台前端/设计解决方案。本地环境需要安装 node 和 git,技术栈基于 ES2015+、React、dva、g2 和 antd。
参考:https://dvajs.com/
https://github.com/ant-design/ant-design-pro/blob/master/README.zh-CN.md
https://pro.ant.design/docs/getting-started-cn
1、预备知识
1)Redux 是 JavaScript 状态容器,提供可预测化的状态管理;Redux 除了和 React 一起用外,还支持其它界面库。
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]):
连接 React 组件与 Redux store。
[mapStateToProps(state, [ownProps]): stateProps
] (Function): 如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps
函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。
-
函数将被调用两次。第一次是设置参数,第二次是组件与 Redux store 连接:
connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)
。 -
connect 函数不会修改传入的 React 组件,返回的是一个新的已与 Redux store 连接的组件,而且你应该使用这个新组件。
-
mapStateToProps
函数接收整个 Redux store 的 state 作为 props,然后返回一个传入到组件 props 的对象。
注入 dispatch
和 todos
function mapStateToProps(state) {
return { todos: state.todos }
}
export default connect(mapStateToProps)(TodoApp)
// 注入 dispatch 和全局 state
export default connect(state => state)(TodoApp)
// 不要这样做!这会导致每次 action 都触发整个 TodoApp 重新渲染
// 最好在多个组件上使用 connect(),每个组件只监听它所关联的部分 state。
Action 是把数据从应用(这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch()
将 action 传到 store。
Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type
字段来表示将要执行的动作。
2)redux-saga
是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。
redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。
effects: {
*create({ payload: values }, { call, put }) {
yield call(usersService.create, values);
yield put({ type: ''reload'' });
},
*reload(action, { put, select }) {
const page = yield select(state => state.users.page);
yield put({ type: ''fetch'', payload: { page } });
},
}
call(fn, ...args)
创建一个 Effect 描述信息,用来命令 middleware 以参数 args
调用函数 fn
。
fn: Function
- 一个 Generator 函数, 也可以是一个返回 Promise 或任意其它值的普通函数。args: Array<any>
- 传递给fn
的参数数组。
put(action)
创建一个 Effect 描述信息,用来命令 middleware 向 Store 发起一个 action。 这个 effect 是非阻塞型的,并且所有向下游抛出的错误(例如在 reducer 中),都不会冒泡回到 saga 当中。
select(selector, ...args)
创建一个 Effect,用来命令 middleware 在当前 Store 的 state 上调用指定的选择器。
-
selector: Function
- 一个(state, ...args) => args
的函数。它接受当前 state 和一些可选参数,并返回当前 Store state 上的一部分数据。
2、dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。
dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念。dva 帮你自动化了Redux 架构一些繁琐的步骤,比如redux store 的创建,中间件的配置,路由的初始化等等,只需写几行代码就可以实现上述步骤。
1)使用 antd
通过 npm 安装 antd
和 babel-plugin-import
,babel-plugin-import
是用来按需加载 antd 的脚本和样式的;编辑 .webpackrc
,使 babel-plugin-import
插件生效。
// .webpackrc.js
extraBabelPlugins: [[''import'', { libraryName: ''antd'', libraryDirectory: ''es'', style: true }]]
2)dva应用
// src/index.js 入口js
import dva from ''dva'';
import browserHistory from ''history/createBrowserHistory'';
import createLoading from ''dva-loading'';
// 1. Initialize
const app = dva({
history: browserHistory(),
});
// 2. Plugins
app.use(createLoading());
// 3. Model
app.model(require(''./models/global'').default);
app.model(require(''./models/menu'').default);
// 4. Router
app.router(require(''./router'').default);
// 5. Start
app.start(''#root''); // 启动应用
app = dva(opts)-》
创建应用,返回 dva 实例。(注:dva 支持多实例)
opts
包含:
history
:指定给路由用的 history,默认是hashHistory
2)定义路由
app.router(({ history, app }) => RouterConfig)
注册路由表,推荐把路由信息抽成一个单独的文件,这样结合 babel-plugin-dva-hmr 可实现路由和组件的热加载(只更新页面修改的部分,不会刷新整个页面)。
// .webpackrc.js
env: {
development: {
extraBabelPlugins: [''dva-hmr''],
},
},
3)定义 Model(处理数据和逻辑)
dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
import * as usersService from ''../services/users'';
export default {
namespace: ''users'',
state: {
list: [],
total: null,
page: null,
},
reducers: {
save(state, { payload: { data: list, total, page } }) {
return { ...state, list, total, page };
},
},
effects: {
*fetch({ payload: { page = 1 } }, { call, put }) {
const { data, headers } = yield call(usersService.fetch, { page });
yield put({
type: ''save'',
payload: {
data,
total: parseInt(headers[''x-total-count''], 10),
page: parseInt(page, 10),
},
});
},
*remove({ payload: id }, { call, put }) {
yield call(usersService.remove, id);
yield put({ type: ''reload'' });
},*reload(action, { put, select }) {
const page = yield select(state => state.users.page);
yield put({ type: ''fetch'', payload: { page } });
},
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname, query }) => {
if (pathname === ''/users'') {
dispatch({ type: ''fetch'', payload: query });
}
});
},
},
};
namespace:model 的命名空间,同时也是他在全局 state 上的属性
state:初始值
reducers:以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 state
的地方。由 action
触发
effects:以 key/value 格式定义 effect。用于处理异步操作和业务逻辑,不直接修改 state
。由 action
触发,可以触发 action
,可以和服务器交互,可以获取全局 state
的数据等等。
subscriptions:以 key/value 格式定义 subscription。subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start()
时被执行,数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
app.model(model)-》
注册 model
4)编写UI Component并connect起来
import React from ''react'';
import { connect } from ''dva'';
import { Table, Pagination, Popconfirm, Button } from ''antd'';
import { routerRedux } from ''dva/router'';
import styles from ''./Users.css'';
import { PAGE_SIZE } from ''../../../../constants'';
import UserModal from ''./UserModal'';
function Users({ dispatch, list: dataSource, loading, total, page: current }) {
function deleteHandler(id) {
dispatch({
type: ''users/remove'',
payload: id,
});
}
function pageChangeHandler(page) {
dispatch(
routerRedux.push({
pathname: ''/users'',
query: { page },
})
);
}
const columns = [
{
title: ''Username'',
dataIndex: ''username'',
key: ''username'',
render: text => <a href="">{text}</a>,
},
{
title: ''Street'',
dataIndex: ''address.street'',
key: ''street'',
},
{
title: ''Website'',
dataIndex: ''website'',
key: ''website'',
},
{
title: ''Operation'',
key: ''operation'',
render: (text, record) => (
<span className={styles.operation}>
<Popconfirm title="Confirm to delete?" onConfirm={deleteHandler.bind(null, record.id)}>
<a href="">Delete</a>
</Popconfirm>
</span>
),
},
];
return (
<div className={styles.normal}>
<div>
<Table
columns={columns}
dataSource={dataSource}
loading={loading}
rowKey={record => record.id}
pagination={false}
/>
<Pagination
className="ant-table-pagination"
total={total}
current={current}
pageSize={PAGE_SIZE}
onChange={pageChangeHandler}
/>
</div>
</div>
);
}
function mapStateToProps(state) {
const { list, total, page } = state.users;
return {
loading: state.loading.models.users,
list,
total,
page,
};
}
export default connect(mapStateToProps)(Users);
5)相关概念
dva 提供了 connect 方法,这个 connect 就是 react-redux 的 connect 。 connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。connect 方法传入的第一个参数是 mapStateToProps 函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch
发起一个 action,如果是同步行为会直接通过 Reducers
改变 State
,如果是异步行为(副作用)会先触发 Effects
然后流向 Reducers
最终改变 State。
Model 对象的属性
- namespace: 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成
- state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
- reducers: Action 处理器,处理同步动作,用来算出最新的 State
- effects:Action 处理器,处理异步动作
Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type
属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch
函数;需要注意的是 dispatch
是在组件 connect Models以后,通过 props 传入的。在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects
dispatch({
type: ''user/add'', // 如果在 model 外调用,需要添加 namespace
payload: {}, // 需要传递的信息
});
Reducer函数接受两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。
state: {
list: [],
total: null,
page: null,
},
reducers: {
save(state, { payload: { data: list, total, page } }) {
return { ...state, list, total, page };
},
}
Effect:Action 处理器,处理异步动作,基于 Redux-saga 实现。Effect 指的是副作用。根据函数式编程,计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写。
dva 提供多个 effect 函数内部的处理函数,比较常用的是 call
和 put
。
- call:执行异步函数
- put:发出一个 Action,类似于 dispatch
effects: {
*create({ payload: values }, { call, put }) {
yield call(usersService.create, values);
yield put({ type: ''reload'' });
},
*reload(action, { put, select }) {
const page = yield select(state => state.users.page);
yield put({ type: ''fetch'', payload: { page } });
},
}
Router:这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 History API 可以监听浏览器url的变化,从而控制路由相关操作。
dva 实例提供了 router 方法来控制路由,使用的是react-router。
在组件设计方法中,我们提到过 Container Components,在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。
所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在/routes/
目录下,而/components/
目录下则是纯组件。
组件设计
React 应用是由一个个独立的 Component 组成的,我们在拆分 Component 的过程中要尽量让每个 Component 专注做自己的事。
一般来说,我们的组件有两种设计:Container Component、Presentational Component
- Container Component
Container Component 一般指的是具有监听数据行为
的组件,一般来说它们的职责是绑定相关联的 model 数据
,以数据容器的角色包含其它子组件。
- Presentational Component
它不会关联订阅 model 上的数据,而所需数据的传递则是通过 props 传递到组件内部。
对组件分类,主要有两个好处:让项目的数据处理更加集中;让组件高内聚低耦合,更加聚焦;
试想如果每个组件都去订阅数据 model,那么一方面组件本身跟 model 耦合太多,另一方面代码过于零散,到处都在操作数据,会带来后期维护的烦恼。
除了写法上订阅数据的区别以外,在设计思路上两个组件也有很大不同。 Presentational Component
是独立的纯粹的,可以参考 ant.design UI组件的React实现 ,每个组件跟业务数据并没有耦合关系,只是完成自己独立的任务,需要的数据通过 props
传递进来,需要操作的行为通过接口暴露出去。 而 Container Component
更像是状态管理器,它表现为一个容器,订阅子组件需要的数据,组织子组件的交互逻辑和展示。
3、其它
1)roadhog-》和 webpack 相似的库,起的是 webpack 自动打包和热更替的作用
roadhog 是一个 cli 工具,提供 dev、 build
和 test
三个命令,分别用于本地调试、构建和测试,并且提供了特别易用的 mock 功能。在体验上,保持了和 create-react-app一致(如 redbox 显示出错信息、HMR、ESLint 出错提示等等),并且提供了 JSON 格式的配置方式。如果 create-react-app 的默认配置不能满足需求,而他又不提供定制的功能,于是基于他实现了一个可配置版。所以如果既要 create-react-app 的优雅体验,又想定制配置,那么可以试试 roadhog 。
## Install globally or locally
$ npm i roadhog -g
## Local development
$ roadhog dev
## Build
$ roadhog build
## Test
$ roadhog test
roadhog dev支持mock, 在.roadhogrc.mock.js里配置
export default {
// Support type as Object and Array
''GET /api/users'': { users: [1,2] },
// Method like GET or POST can be omitted(省略)
''/api/users/1'': { id: 1 },
// Support for custom functions, the API is the same as express@4
''POST /api/users/create'': (req, res) => { res.end(''OK''); },
};
roadhog的webpack部分是基于af-webpack的实现。在项目根目录创建 .webpackrc进行配置,格式是
JSON。
2)react-router-redux和dva
redux 是状态管理的库,router 是(唯一)控制页面跳转的库。两者都很美好,但是不美好的是两者无法协同工作。换句话说,当路由变化以后,store 无法感知到。于是便有了 react-router-redux
。
react-router-redux
是 redux 的一个中间件,主要作用是:加强了React Router库中history这个实例,以允许将history中接受到的变化反应到state中去。
从代码上讲,主要是监听了 history 的变化。dva 在此基础上又进行了一层代理,把代理后的对象当作初始值传递给了 dva-core,方便其在 model 的 subscriptions 中监听 router 变化。
3)dva/fetch-》异步请求库,输出 isomorphic-fetch 的接口。
4)dva-loading
dva 有一个管理 effects 执行的 hook,并基于此封装了 dva-loading 插件。通过这个插件,我们可以不必一遍遍地写 showLoading 和 hideLoading,当发起请求时,插件会自动设置数据里的 loading 状态为 true 或 false 。然后我们在渲染 components 时绑定并根据这个数据进行渲染。
// 1、注册 dva-loading 插件
import dva from ''dva'';
import createLoading from ''dva-loading'';
const app = dva();
app.use(createLoading());
// 2、从store中获取loading状态
import React from ''react'';
import { connect } from ''dva'';
import { Table } from ''antd'';
function Users({ dispatch, list: dataSource, loading }) {
const columns = [
{
title: ''Username'',
dataIndex: ''username'',
key: ''username'',
render: text => <a href="">{text}</a>,
},
{
title: ''Street'',
dataIndex: ''address.street'',
key: ''street'',
},
{
title: ''Website'',
dataIndex: ''website'',
key: ''website'',
}
];
return (
<div className={styles.normal}>
<Table
columns={columns}
dataSource={dataSource}
loading={loading}
rowKey={record => record.id}
pagination={false}
/>
</div>
);
}
function mapStateToProps(state) {
const { list } = state.users;
return {
loading: state.loading.models.users,
list,
};
}
export default connect(mapStateToProps)(Users);
2、项目积累
1)React 中常见模式是为一个组件返回多个元素。为了包裹多个元素我们写过很多的 div 和 span,进行不必要的嵌套,无形中增加了浏览器的渲染压力。
react15版以前,render 函数的返回必须有一个根节点,否则报错,为满足这一原则我会使用一个没有任何样式的 div 包裹一下。
import React from ''react'';
export default function () {
return (
<div>
<div>一步 01</div>
<div>一步 02</div>
<div>一步 03</div>
</div>
);
}
react 16版开始, render支持返回数组,这一特性已经可以减少不必要节点嵌套。
import React from ''react'';
export default function () {
return [
<div>一步 01</div>,
<div>一步 02</div>,
<div>一步 03</div>
];
}
而且,React 16为我们提供了Fragment。Fragment与Vue.js的<template>
功能类似,可做不可见的包裹元素。
import React from ''react'';
export default function () {
return (
<React.Fragment>
<div>一步 01</div>
<div>一步 02</div>
<div>一步 03</div>
</React.Fragment>
);
}
参考:https://segmentfault.com/a/1190000013220508
附录:es6
1)Generator 函数
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态。
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
function* helloWorldGenerator() {
yield ''hello'';
yield ''world'';
return ''ending'';
}
var hw = helloWorldGenerator();
上面代码定义了一个 Generator 函数helloWorldGenerator
,它内部有两个yield
表达式(hello
和world
),即该函数有三个状态:hello,world 和 return 语句(结束执行)。
然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象—遍历器对象。
下一步,必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
hw.next() // { value: ''hello'', done: false }
hw.next() // { value: ''world'', done: false }
hw.next() // { value: ''ending'', done: true }
hw.next() // { value: undefined, done: true }
遍历器对象的next
方法的运行逻辑如下。
(1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
(4)如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。另外需要注意,yield
表达式只能用在 Generator 函数里面,用在其他地方都会报错。
2)Generator 函数的异步应用
ES6 诞生以前,异步编程的方法,大概有四种:回调函数、事件监听、发布/订阅、Promise 对象。Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。
create-react-app 引入 antd 及 解决 antd 样式无法显示的bug
方案一: npm run eject 暴露所有内建的配置
安装组件库
yarn add antd babel-plugin-import
根目录下新建.roadhogrc
文件(别忘了前面的点,这是roadhog工具的配置文件,下面的代码用于加载上一个命令安装的import插件),写入:
{
"extraBabelPlugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "lib",
"style": "css"
}]
]
}
antd配置
修改 webpack.config.dev.js 和 webpack.config.prod.js文件,这里以webpack.config.dev.js举例,
webpack.config.prod.js一样配置即可:
// Process JS with Babel.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve(''babel-loader''),
options: {
// 改动: 添加 antd 按需加载文件处理插件
plugins: [
[''react-html-attrs''],//添加babel-plugin-react-html-attrs组件的插件配置
// 引入样式为 css
[''import'', { libraryName: ''antd'', style: ''css'' }],
// 改动: 引入样式为 less
// [''import'', { libraryName: ''antd'', style: true }],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
},
},
引入模块如下:
// scr/App.js
import React, { Component } from ''react'';
- import Button from ''antd/lib/button'';
+ import { Button } from ''antd'';
import ''./App.css'';
方案二:React-app-rewired(一个对 create-react-app 进行自定义配置的社区解决方案)
1. 安装react-app-rewired
npm install –save-dev react-app-rewired
2.修改package.json启动项
/* package.json */
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
}
3.在项目根目录创建一个 config-overrides.js 用于修改默认配置。
module.exports = function override(config, env) {
// do stuff with the webpack config...
return config;
};
4.使用babel-plugin-import实现Antd按需加载,修改config-overrides.js
npm install –save-dev babel-plugin-import
config-overrides.js
/* config-overrides.js */
const { injectBabelPlugin } = require(''react-app-rewired'');
module.exports = function override(config, env) {
config = injectBabelPlugin([''import'', { libraryName: ''antd'', style: ''css''}], config);
return config;
};
5.使用react-app-rewire-less配置Less
npm install –save-dev react-app-rewire-less
config-overrides.js
/* config-overrides.js */
const { injectBabelPlugin } = require(''react-app-rewired'');
const rewireLess = require(''react-app-rewire-less'');
module.exports = function override(config, env) {
config = injectBabelPlugin([''import'', { libraryName: ''antd'', style: true }], config);
config = rewireLess.withLoaderOptions({
modifyVars: { "@primary-color": "#1DA57A" },
})(config, env);
return config;
};
我遇到的问题: 1. \__DEV__ is not defined.
npm install –save-dev react-app-rewire-defind-plugin
config-overrides.js
/* config-overrides.js */
const { injectBabelPlugin } = require(''react-app-rewired'');
const rewireLess = require(''react-app-rewire-less'');
const rewireDefinePlugin = require(''react-app-rewire-define-plugin'');
module.exports = function override(config, env) {
config = injectBabelPlugin([''import'', { libraryName: ''antd'', style: true }], config);
config = rewireLess.withLoaderOptions({
modifyVars: { "@primary-color": "#1DA57A" },
})(config, env);
config = rewireDefinePlugin(config, env, {
__DEV__: false
});
return config;
};
注:在执行 yarn build 进行打包部署后,antd样式没有加载进去
解决方案:生产部署增加对antd的支持
// Process JS with Babel.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve(''babel-loader''),
options: {
// 改动: 添加 antd 按需加载文件处理插件
plugins: [
[''react-html-attrs''],//添加babel-plugin-react-html-attrs组件的插件配置
// 引入样式为 css
[''import'', { libraryName: ''antd'', style: ''css'' }],
// 改动4: 引入样式为 less
// [''import'', { libraryName: ''antd'', style: true }],
],
compact: true,
},
},
create-react-app+antd+react-css-modules配置
快速开始:
npminstall-gcreate-react-app /*安装create-react-apP*/ npminstall-gcreate-react-app /*安装yarn*/ create-react-appmyapp /*使用命令创建应用,myapp为项目名称*/ cdmyapp /*进入目录 */ yarn start /* 启动项目 */
按以上执行,即可快速创建React开发环境。
打开http://localhost:3000/ 查看
环境配置:
生成项目后,脚手架为了“优雅”... ...隐藏了所有的webpack相关的配置文件,此时查看myapp文件夹目录,会发现找不到任何webpack配置文件。执行以下命令:
yarn eject
再查看myapp 文件夹,可以看到完整的项目结构:
myapp/ node_modules/ package.json .gitignore config/ paths.js polyfills env.js webpack.config.dev.js /* 开发环境配置文件 */ webpack.config.prod.js /* 生产环境配置文件 */ public/ index.html /*入口html文件*/ scripts/ build.js start.js test.js src/ App.js index.js /*主入口文件*/
antd配置
yarn add antd babel-plugin-import
按需引入
为减少打包后体积以及方便书写,可用babel-plugin-import插件,配置在config目录下,
webpack.config.dev.js 和 webpack.config.prod.js文件,这里以webpack.config.dev.js举例,
webpack.config.prod.js一样配置即可:
// Process JS with Babel. { test: /\.(js|jsx)$/,include: paths.appSrc,loader: require.resolve('babel-loader'),options: { // 改动: 添加 antd 按需加载文件处理插件 plugins: [ //['react-html-attrs'],//添加babel-plugin-react-html-attrs组件的插件配置 // 引入样式为 css ['import',{ libraryName: 'antd',style: 'css' }],// 改动: 引入样式为 less // ['import',style: true }],],// This is a feature of `babel-loader` for webpack (not Babel itself). // It enables caching results in ./node_modules/.cache/babel-loader/ // directory for faster rebuilds. cacheDirectory: true,},
引入模块如下:
// scr/App.js import React,{ Component } from 'react'; - import Button from 'antd/lib/button'; + import { Button } from 'antd'; import './App.css';
CSS Modules 配置
antd 和 css modules 不能混用,看知乎大神说 可以
针对antd的css 单独写一条loader的规则,不开启 css modules。
使用 exclude 和 include 配置
配置在config目录下,同样修改配置 webpack.config.dev.js 文件
{ test: /\.css$/,exclude: /node_modules|antd\.css/,use: [ require.resolve('style-loader'),{ loader: require.resolve('css-loader'),options: { importLoaders: 1,// 改动 modules: true,// 新增对css modules的支持 localIdentName: '[name]__[local]__[hash:base64:5]',// },{ loader: require.resolve('postcss-loader'),options: { // Necessary for external CSS imports to work // https://github.com/facebookincubator/create-react-app/issues/2677 ident: 'postcss',plugins: () => [ require('postcss-flexbugs-fixes'),autoprefixer({ browsers: [ '>1%','last 4 versions','Firefox ESR','not ie < 9',// React doesn't support IE8 anyway ],flexBox: 'no-2009',}),// { test: /\.css$/,include: /node_modules|antd\.css/,// 改动 // modules: true,// 新增对css modules的支持 // localIdentName: '[name]__[local]__[hash:base64:5]',
下面是一个 antd 的 Button 和自己写的CSS Modules 使用小例子:
import React,{ Component } from 'react'; import { addRecipe } from '../actions'; import { Button } from 'antd'; import styles from './App.css' class App extends Component { state = { calendar: null } componentDidMount() { const { store } = this.props // 从props获取store store.subscribe(() => { // 订阅 redux store 中发生的任何变化 this.setState(() => ({ // 有任何变化需要调用 setState calendar: store.getState() // 将从store中获取state将它放入本地组件state中 })) }) } submitFood = () => { this.props.store.dispatch(addRecipe({//store.dispatch调用addRecipe动作生成器 day: 'monday',meal: 'breakfast',recipe: { label: this.input.value } })) this.input.value = ''//将输入值重置为一个空的字符串 } render() { return ( <div> <input type='text' ref={(input)=>this.input = input} placeholder="Monday's Breakfast" /> <Button type="primary" onClick={this.submitFood}>Submit</Button> <pre className={styles.input}>Monday's Breakfast:{this.state.calendar&&this.state.calendar.monday.breakfast}</pre> </div> ); } } export default App
参考学习 CSS Modules 的阮一峰老师写的用法教程
这里简单使用下 react-css-modules,代码如下:
import React,{ Component } from 'react'; import { addRecipe } from '../actions'; import { Button } from 'antd'; import CSSModules from 'react-css-modules' import styles from './App.css' class App extends Component { state = { calendar: null } componentDidMount() { const { store } = this.props // 从props获取store store.subscribe(() => { // 订阅 redux store 中发生的任何变化 this.setState(() => ({ // 有任何变化需要调用 setState calendar: store.getState() // 将从store中获取state将它放入本地组件state中 })) }) } submitFood = () => { this.props.store.dispatch(addRecipe({//store.dispatch调用addRecipe动作生成器 day: 'monday',recipe: { label: this.input.value } })) this.input.value = ''//将输入值重置为一个空的字符串 } render() { return ( <div> <input type='text' ref={(input)=>this.input = input} placeholder="Monday's Breakfast" /> <Button type="primary" onClick={this.submitFood}>Submit</Button> <pre styleName='input'>Monday's Breakfast:{this.state.calendar&&this.state.calendar.monday.breakfast}</pre> </div> ); } } export default CSSModules(App,styles);
主要差别在于:
+ import CSSModules from 'react-css-modules' - <pre className={styles.input}> + <pre styleName='input'> - export default App + export default CSSModules(App,styles);
create-react-app同时对多个框架(antd+antd-mobile)做按需加载的方法
在React项目开发中,经常需要引用一些实用的第三方框架。在使用一些比较庞大的第三方框架时,框架内的各种资源文件数量巨大,这时,如果我们在每次使用框架时,都将框架内所有资源都全部加载的话,这将使得页面的性能大大降低。这时,我们就需要对这些庞大的第三方框架做按需加载了。
首先介绍下对单个框架做按需加载的方法
其实在使用create-react-app脚手架的情况下,对单个框架做按需加载的方法,网上的相关文章已经很多了,我这里只简单的介绍下。常用的方法就是通过babel-plugin-import来实现按需加载,并通过react-app-rewired来重写项目配置文件,将babel-plugin-import写入配置。
1、安装
cnpm install babel-plugin-import --dev
cnpm install react-app-rewired --dev
2、修改package.json
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
3、在项目的根目录下创建一个 config-overrides.js 用于修改默认配置
const {injectBabelPlugin} = require(''react-app-rewired'');
const rewireLess = require(''react-app-rewire-less'');
const path = require(''path'')
module.exports = function override(config, env) {
config = injectBabelPlugin(
[''import'',
{
libraryName: ''antd'',
libraryDirectory: ''es'',
style: true
}
],
config
);
config = rewireLess.withLoaderOptions({
modifyVars: {"@primary-color": "#4197FC"},
javascriptEnabled: true,
})(config, env);
return config;
};
这样就完成了对antd的按需加载
那么对多个框架做按需加载应该怎么做呢?
对多个框架做按需加载的方法
这里拿antd和antd-mobile两个框架来举例子
首先还是要按照上面的步骤安装babel-plugin-import和react-app-rewired,并修改默认配置,区别只是在最后一步上。在调用babel-plugin-import的injectBabelPlugin方法时,需要调用两次,并注明相对应的框架名。具体代码如下:
const {injectBabelPlugin} = require(''react-app-rewired'');
const rewireLess = require(''react-app-rewire-less'');
const path = require(''path'')
module.exports = function override(config, env) {
config = injectBabelPlugin(
[''import'',
{
libraryName: ''antd'',
libraryDirectory: ''es'',
style: true
}, ''ant''
],
config
);
config = injectBabelPlugin(
[''import'',
{
libraryName: "antd-mobile",
libraryDirectory: ''lib'',
style: true
}, ''ant-mobile''
],
config
);
config = rewireLess.withLoaderOptions({
modifyVars: {"@primary-color": "#4197FC"},
javascriptEnabled: true,
})(config, env);
return config;
};
关于用户管理的 CURD 应用 (react+dva+antd)和用户管理的三种功能的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于Ant Design Pro(React/dva/antd)、create-react-app 引入 antd 及 解决 antd 样式无法显示的bug、create-react-app+antd+react-css-modules配置、create-react-app同时对多个框架(antd+antd-mobile)做按需加载的方法等相关内容,可以在本站寻找。
本文标签: