初始化
安装dva-cli用户初始化项目
npm install -g dva-cli
# 或
yarn global add dva-cli
dva -v
dva-cli version 0.9.1
创建新应用
安装完dva-cli之后,可以在命令行里访问到dva命令。现在,可以通过dva new 创建新应用
dva new dva-quickstart
这会创建 dva-quickstart
目录,包含项目初始化目录和文件,并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。
然后运行 npm start
或 yarn start
即可运行项目。
目录结构
项目初始化以后,默认的目录结构如下:
|- mock
|- node_modules
|- package.json
|- public
|- src
|- asserts
|- components
|- models
|- routes
|- services
|- utils
|- router.js
|- index.js
|- index.css
|- .editorconfig
|- .eslintrc
|- .gitignore
|- .roadhogrc.mock.js
|- .webpackrc
- mock 存放用于 mock 数据的文件;
- public 一般用于存放静态文件,打包时会被直接复制到输出目录(./dist);
- src 文件夹用于存放项目源代码;
- asserts 用于存放静态资源,打包时会经过 webpack 处理;
- components 用于存放 React 组件,一般是该项目公用的无状态组件;
- models 用于存放模型文件
- routes 用于存放需要 connect model 的路由组件;
- services 用于存放服务文件,一般是网络请求等;
- utils 工具类库
- router.js 路由文件
- index.js 项目的入口文件
- index.css 一般是共用的样式
- .editorconfig 编辑器配置文件
- .eslintrc ESLint配置文件
- .gitignore Git忽略文件
- .roadhogrc.mock.js Mock配置文件
- .webpackrc 自定义的webpack配置文件,JSON格式,如果需要 JS 格式,可修改为 .webpackrc.js
项目中使用antd
通过npm安装antd和babel-plugin-import。babel-plugin-import是用来按需加载antd的脚本和样式。(详情https://ant.design/docs/react/getting-started-cn#%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD)
npm install antd babel-plugin-import --save
编辑 .webpackrc
,使 babel-plugin-import
插件生效。
{
"extraBabelPlugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}]
]
}
现在就可以按需引入 antd 的组件了,如 import { Button } from 'antd'
,Button 组件的样式文件也会自动帮你引入。
更多 .webpackrc
的配置请参考 roadhog 配置。(https://github.com/sorrycc/roadhog/blob/master/README_zh-cn.md#%E9%85%8D%E7%BD%AE)
开发代理
如需开发过程中代理API接口,在.webpackrc
中添加如下配置即可:
{
"proxy": {
"/api": {
"target": "http://your-api-server",
"changeOrigin": true
}
}
}
.webpackrc
{
"extraBabelPlugins": [ //项目中按需加载antd
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}]
],
"proxy": { // 请求代理
"/api": {
"target": "http://your-api-server",
"changeOrigin": true
}
},
"env": { //热更新
"development": {
"extraBabelPlugins": [
"dva-hmr"
]
}
}
}
Mock
如需mock功能,在 .roadhogrc.mock.js
中添加配置即可,如:
export default {
'GET /api/users': { users: [{ username: 'admin' }] },
}
如上配置,当请求/api/users时会返回JSON格式的数据
同时也支持自定义函数,如下:
export default {
'POST /api/users': (req, res) => { res.end('OK'); },
}
具体的 API 请参考 Express.js@4。
当 mock 数据太多时,可以拆分后放到 ./mock
文件夹中,然后在 .roadhogrc.mock.js
中引入。
HMR
HTR,即模块热替换,在修改代码后不需要刷新整个页面,方便开发时的调试。可以在.webpackrc
中添加如下配置以使用 HMR:
{
"env": {
"development": {
"extraBabelPlugins": [
"dva-hmr"
]
}
}
}
如果无效,请尝试更新一下 babel-plugin-dva-hmr
。
env
字段是针对特定环境进行配置,因为 HMR 只在开发环境下使用,所以将配置添加到 development
字段即可,运行 npm run build
时的环境变量为 production
。
定义路由
我们要写个应用来显示产品列表。首先第一步是创建路由,路由可以想象成组件应用的不同页面。
新建 route component routes/Products.js
,内容如下:
import React from 'react';
const Products = (props) => (
<h2>List of Products</h2>
);
export default Products;
添加路由信息到路由表,编辑 router.js
:
+ import Products from './routes/Products';
...
+ <Route path="/products" exact component={Products} />
然后在浏览器里打开 http://localhost:8000/#/products ,你应该能看到前面定义的 <h2>
标签
编写 UI Component
随着应用的不断拓展,需要多个页面共用公共组件,直接将组件抽离成公共组件,以在项目中实现共用。
编写一个ProductList
新建 components/ProductList.js
文件:
import React from 'react';
import PropTypes from 'prop-types';
import { Table, Popconfirm, Button } from 'antd';
const ProductList = ({ onDelete, products }) => {
const columns = [{
title: 'Name',
dataIndex: 'name',
}, {
title: 'Actions',
render: (text, record) => {
return (
<Popconfirm title="Delete?" onConfirm={() => onDelete(record.id)}>
<Button>Delete</Button>
</Popconfirm>
);
},
}];
return (
<Table
dataSource={products}
columns={columns}
/>
);
};
ProductList.propTypes = {
onDelete: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
};
export default ProductList;
定义Model
完成UI后,处理数据和逻辑。
Model是dva最重要的部分,可以理解为redux、react-redux、redux-saga的封装。通常项目中一个模块对应一个model,
dva通过Model的概念把一个领域的模型管理起来,包含同步更新state的reducers,处理异步逻辑的effects,订阅数据源的subscriptions
export default {
namespace: 'products',
state: [],
reducers: {
'delete'(state, { payload: id }) {
return state.filter(item => item.id !== id);
},
},
};
import { fetchUsers } from '../services/user';
export default {
namespace: 'user',
state: {
list: [],
},
reducers: {
save(state, action) {
return {
...state,
list: action.data,
};
},
},
effects: {
*fetch(action, { put, call }) {
const users = yield put(fetchUsers, action.data);
yield put({ type: 'save', data: users });
},
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname }) => {
if (pathname === '/user') {
dispatch({ type: 'fetch' });
}
});
},
},
}
这个 model 里:
namespace
表示在全局 state 上的 key,是该model的命名空间,同时也是全局state上的一个属性,只能是字符串,不支持使用.创建多层命名空间。state
是初始值,在这里是空数组reducers
等同于 redux 里的 reducer,接收 action,同步更新 state,纯函数,用于处理同步操作,是唯一可以修改state的地方,由action触发,它有state和action两个参数- effects用于处理异步操作,不能直接修改state,由action触发,也可以触发action。只能是generator函数,并且有action和effects两个参数。第二个参数effects包含put、call和select三个字段,put用于触发action,call用于调用异步处理逻辑,select用于从state中获取数据。
subscriptions
用于订阅某些数据源,并根据情况 dispatch 某些 action,格式为({ dispatch, history }, done) => unlistenFunction
。
如上的一个model,监听路由变化,当进入/user页面时,执行effects中的fetch,以从服务端获取用户列表,然后fetch中触发reducers中的save将从服务端获取到的数据保存到state中。
注意,在model中触发这个model中的action时不需要命名空间,比如在fetch中触发save时是{ type: 'save' }。在组件中触发action时就需要带上命名空间了,比如在某个组件中触发fetch时,应该是{ type: 'user/fetch' }
然后别忘记在 index.js
里载入他:
app.model(require('./models/products').default);
connect 起来
这里,我们已经单独完成了model和component,那么他们如何串联起来呢?
dva提供了connect方法。如果你熟悉redux,这个connect就是react-redux的connect。
编辑 routes/Products.js,替换为以下内容:
import React from 'react';
import { connect } from 'dva';
import ProductList from '../components/ProductList';
const Products = ({ dispatch, products }) => {
function handleDelete(id) {
dispatch({
type: 'products/delete',
payload: id,
});
}
return (
<div>
<h2>List of Products</h2>
<ProductList onDelete={handleDelete} products={products} />
</div>
);
};
// export default Products;
export default connect(({ products }) => ({
products,
}))(Products);
connect 后的组件除了可以获取到 dispatch
和 state
,还可以获取到 location
和 history
。
最后,我们还需要一些初始数据让这个应用 run 起来。编辑 index.js
:
const app = dva({
initialState: {
products: [
{ name: 'dva', id: 1 },
{ name: 'antd', id: 2 },
],
},
});
异步请求
dva 集成了 isomorphic-fetch
用于处理异步请求,并且使用 dva-cli 初始化的项目中,已经在 ./src/utils/request.js
中对 fetch 进行了简单的封装,可以在这里根据服务端 API 的数据结构进行统一的错误处理。
当然如果你不想用 fetch,完全可以引入自己喜欢的第三方库,没有任何影响,打包时也不会将 isomorphic-fetch
打包进去。
参考链接
- babel-plugin-import
- roadhog
- dva.js 知识导图
- dva.js API
- dva.js 概念
- dva + antd 项目实战
- https://www.jianshu.com/p/c7b3b9c98d04