持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
前言
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以dva是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装。
是由阿里架构师 sorrycc 带领 team 完成的一套前端框架。
需求
快速搭建基于react的项目(PC端,移动端)。
数据流向和最简结构
数据流向
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致
最简结构
dva = React-Router + Redux + Redux-saga
- 不带model
import dva from 'dva';
const App = () => <div>Hello dva</div>;
// 创建应用
const app = dva();
// 注册视图
app.router(() => <App />);
// 启动应用
app.start('#root');
- 带model
// 创建应用
const app = dva();
// 注册 Model
app.model({
namespace: 'count',
state: 0,
reducers: {
add(state) {
return state + 1;
},
},
effects: {
*addAfter1Second(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'add' });
},
},
});
// 注册视图
app.router(() => <ConnectedApp />);
// 启动应用
app.start('#root');
搭建项目
初始化项目
-
安装node
-
安装dva-cli
$ npm install dva-cli -g
$ dva -v (校验安装的dva版本,我安装的版本是0.10.1)
- 创建新应用
$dva new dva-quickstart(项目名)
- 启动项目
cd dva-quickstart(项目名)
npm start
打开如下窗口
- 安装antd
cnpm install antd babel-plugin-import --save
- 编辑 .webpackrc,使 babel-plugin-import 插件生效
{
"extraBabelPlugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
]
}
项目架构
|-mock //存放用于 mock 数据的文件
|-node_modules //项目包
|-public //一般用于存放静态文件,打包时会被直接复制到输出目录(./dist)
|-src //项目源代码
| |-asserts //用于存放静态资源,打包时会经过 webpack 处理
| |-caches //缓存
| |-components //组件 存放 React 组件,一般是该项目公用的无状态组件
| |-entries //入口
| |-models //数据模型 存放模型文件
| |-pages //页面视图
| |-routes //路由 存放需要 connect model 的路由组件
| |-services //服务 存放服务文件,一般是网络请求等
| |-test //测试
| |-utils //辅助工具 工具类库
|-package.json //包管理代码
|-webpackrc.js //开发配置
|-tsconfig.json // ts配置
|-webpack.config.js //webpack配置
|-.gitignore //Git忽略文件
Dva.js的使用
五个Api(刚配置好时,默认被注释掉,需要取消注释)
import dva from 'dva';
import {message} from 'antd';
import './index.css';
// 1. Initialize 创建 dva 应用实例
const app = dva();
// 2. Plugins 装载插件(可选)
app.use({
onError: function (error, action) {
message.error(error.message || '失败', 5);
}
});
// 3. Model 注册model
app.model(require('../models/example').default);
// 4. Router 配置路由
app.router(require('../routes/router').default);
// 5. Start 启动应用
app.start('#root');
export default app._store; // eslint-disable-line 抛出
app = dva():创建应用,返回 dva 实例。(注:dva 支持多实例)
例如:
const app = dva({
history,
initialState,
onError,
onHmr,
});
app.use():配置 hooks 或者注册插件。
app.model():数据逻辑处理,数据流动。
app.router():注册路由表,做路由跳转
app.start([HTMLElement], opts):启动应用
项目编写
- 定义路由
创建路由,路由可以想象成是组成应用的不同页面
在routes文件夹下创建
新建 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 ,
- 编写UI Component
随着应用的发展,会需要在多个页面分享UI元素(或在一个页面使用多次),在dva里可以把这部分抽成component
在components文件下创建
新建 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 后,开始处理数据和逻辑。
dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。
在models文件夹下创建
新建 model models/products.js :
export default {
namespace: 'products',
state: [],
reducers: {
delete(state, { payload: id }) {
return state.filter((item) => item.id !== id);
},
},
};
这个 model 里:
-
namespace 表示在全局 state 上的 key
-
state 是初始值,在这里是空数组
-
reducers 等同于 redux 里的 reducer,接收 action,同步更新 state
然后别忘记在 index.js 里载入:
app.model(require('./models/products').default);
- connect起来
dva 提供了 connect 方法。这个 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);
最后,需要一些初始数据让这个应用 run 起来。编辑 index.js:
- const app = dva();
const app = dva({
initialState: {
products: [
{ name: 'dva', id: 1 },
{ name: 'antd', id: 2 },
],
},
});
- 构建应用,进行打包
npm run build
- 运行
npm start