# Dva 入门
概念:dva是体验技术部开发的react应用框架,将react-router/redux/redux-saga(异步操作)三个工具库包装在一起,简化API,让react应用更加方便、快捷。
dva是framwork,不是library,类似emberjs,使用依赖库本身的语法。简化开发,相当于一个大的语法糖。
数据流图:start-(connect)->view-(dispatch)->action
state:通过创建一个state对象,保存整个应用状态,
action:是一个对象,用来描述事件;
connect方法:一个函数,绑定state到view;返回的也是react组件,成为容器组件,是原始的UI组件的容器在外面包裹一层state。接收两个参数mapStateToProps函数,mapStateToProps函数返回一个对象,用于建立state到props的映射关系。
dispatch方法:一个函数,发送action到state;被connect的component会自动在props中拥有dispatch方法。
Provider和connect()非常优雅的将全局store提供给了每一个组件。让每一个组件可以非常方便的去使用全局的数据。
roadhog(如同webpack)
1. Model对象的属性
namespace:当前Model的名称。整个应用的state,由多个晓得Model的state以namespace为key合成。
state:该Model当前的状态,数据保存在这里,直接决定了试图层的输出。
reducer:Action处理器,处理同步动作,用来算出最新的state;
effects:Action处理器,处理异步动作;基于redux-saga实现,典型的就是I/O操作、数据库读写。不能直接修改state,由action触发。
Generator函数:effect是一个Generator函数,内部使用yield关键字,表示每一步的操作(不管是异步还是同步)
call和put:dva提供多个effect函数内部的处理函数,常用的是call和put. call是执行异步操作,put发出一个action,类似dispatch.
```
{
namespace: 'count',
state: 0,
reducers: {
add(state) { return state + 1 },
},
effects: {
*addAfter1Second(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'add' });
},
},
}
```
2. dva安装
一、开始新项目的准备工作:
1、安装dva-cli并确保版本在0.9.1或以上版本:
npm install dva-cli -g
检测版本号: dva -v
2、创建项目名为‘wmz’的新项目:
dva new wmz
3、安装antd 和 babel-plugin-import ,babel-plugin-import 是用来按需加载 antd 的脚本和样式的,需要用tnpm 去下载ant插件。
下载tnpm:有的不需要也可以
npm install tnpm -g --registry=http://registry.npm.alibaba-inc.com
tnpm install
下载antd:
npm install antd babel-plugin-import --save
注意:下载完插件后,需要在.webpackrc文件中添加以下内容,才能使 babel-plugin-import 插件生效。
"extraBabelPlugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
]
4、准备工作做完之后,开始启动项目:
npm start
二、新项目实战
1、创建路由,路由可以想象成是组成应用的不同页面。文件的位置:routes文件夹里面。
2、添加路由信息到路由表,在router.js中进行编辑,先引入路由文件,再设置文件路径.
此时,就已经可以在http:127.0.0.1:8000,看到页面信息了;
3、编写UI Component,文件位置:component文件夹里;场景:在多个页面分享UI元素(或者在一个页面使用多次),在dva里可以把这部分抽成component。
4、定义model,dva通过model的概念把一个领域的模型管理起来,包含同步更新state的reducer,处理异步逻辑的effects,订阅数据源的subscriptions。文件的位置在models文件夹里。
在这个model里:
namespace:表示在全局state上的key;
state: 是初始值;
reducer: 等同于redux里的reducer,接收action,同步更新state;
最后记得一定要在index.js文件里载入model:
app.model(require(‘./models/文件名’).default);
5、connect方法:把model和component串联起来。文件位置:routers文件夹里。
6、最后需要在index.js 中添加初始数据让项目run起来;
const app = dva();
三、项目模块安排
项目的整体设计模式:
1. 先设计model
namespace 是 model state 在全局 state 所用的 key,state 是默认数据。
异步处理:dva 通过对 model 增加 effects 属性来处理 side effect(异步任务),call,put;
键盘订阅事件:subscription和model绑定,用于订阅一个数据源,然后根据dispatch需要的action,数据源可以是当前时间、服务器的websocket、keyword输入、geolocation变化、history路由变化等。
2. 再设计component
通过 props 传入两个值,count 和 dispatch,count 对应 model 上的 state,在后面 connect 的时候绑定,dispatch用于分发 action
更新state是通过reducer处理的,也是唯一可以更新state的地方。
3. 最后链接model和component
router:写组件,接收dispatch和products两个参数;
小案例: import React from 'react'; import {connect} from 'dva'; import ProductList from '../components/ProductList'; const Products = ({dispatch,products}) => { function handleDelete(id){ dispatch({//调用dva中的方法 type:'products/delete', payload: id, }) }; return( <div> <h2>list of products</h2> {/*给组件绑定两个时间,删除事件和同步处理数据dva */} <ProductList onDelete={handleDelete} products={products}/> </div> ) } export default connect(({ products})=>({ products, }))(Products); ``` models:定义model,对state进行管理,处理组件中的同步的方法; ``` //在组件中调用 export default { namespace : 'products', state : [], reducers : { 'delete'(state,{payload:id}){//接收两个参数state和delete接收的参数 return state.filter(item =>item.id !== id);//filter返回true或者false,是否保留该数据。 } } } ``` components:一个对象,接收父组件绑定在子组件上的方法和models; ``` 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( //气泡框 onConfirm确认的 <Popconfirm title="删除?" onConfirm={()=>onDelete(record.id)}> <Button>Delete</Button> </Popconfirm> ); } }]; return( <Table //表格 dataSource = {products} columns = {columns} /> ) } export default ProductList;
## Dva源码解析
1. roadhog 起的是 webpack 自动打包和热更替的作用。
2. dva是一个杉树,返回一个app对象;目前 dva 的源码核心部分包含两部分,dva 和 dva-core。前者用高阶组件 React-redux 实现了 view 层,后者是用 redux-saga 解决了 model 层。
3. src/index.js
在这里,dva 做了三件比较重要的事情:
使用 call 给 dva-core 实例化的 app(这个时候还只有数据层) 的 start 方法增加了一些新功能(或者说,通过代理模式给 model 层增加了 view 层)。
使用 react-redux 完成了 react 到 redux 的连接。
添加了 redux 的中间件 react-redux-router,强化了 history 对象的功能。
4. provider本质上是一个高阶组件,也是一种代理模式,接收 redux 生成的 store 做参数后,通过上下文 context 将 store 传递进被代理组件。在保留原组件的功能不变的同时,增加了 store 的 dispatch 等方法。
5. connect 也是一个代理模式实现的高阶组件,为被代理的组件实现了从 context 中获得 store 的方法。
6. initialState 是 state 的初始数据,优先级高于 model 中的 state,默认为 {}。
7. createLoading() 加载状态,在app.use(CreateLoading());根据state.loading.global指示全局loading状态。如果在组件中直接使用this.props.locading
## Dva
1. 解决的主要问题
简化了react-redux的部署文件的复杂度,能够快速上手;
2. 主要接口:
(1)后端接口:
配置路由,在点击按钮后,在componentWillMount中通过this.props.dispatch派发,在model中执行effects、reducer等操作;在servers的文件夹下发送网络请求需要通过request.js文件,后台接收:
export function patch(id, values) {
return request(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(values),
});
}
(2)mock数据:在.roadhogrc.mock.js中添加配置
export default {
'GET /api/users': { users: [{ username: 'admin' }] },
}
export default {
'POST /api/users': (req, res) => { res.end('OK'); },
}
在roadhogrc中进行配置proxy;若是get请求,则可以直接在url后面进行拼接;
3. 主要执行路径
用户操作后或者路由跳转->dispatch(action),同步直接通过reducer更改state,异步先触发effects,然后通过reducer最终改变state.
4. 可配置及修改的类、函数和文件
(1)路由:通过浏览器的History API可以监听浏览器url的变化,从而控制路由相关操作
(2)在入口文件index.js中 可配置plugin/model/router
state:model的状态数据,对象形式;
action:是一个javascript对象,改变state的唯一途径。必须带有type属性,需要注意的是 dispatch 是在组件 connect Models以后,通过 props 传入的;
dispatch:payload是需要传递的信息;通过props传入的dispatch函数;
reducers:{ save(state,{payload: }){ return { }}}返回一个新值;
effects:{fetch({payload},{call,put})} 不能直接修改state,由action触发。call是执行异步操作, put发出一个action,类似dispatch.
import * as 自定义名 from '../services/service';
effects: {
*供组件调用的方法名({ payload: { 参数 }}, { call, put }) {
const result = yield call(自定义名.service中的方法名, 参数);//如果使用 {参数} ,则是一个对象
//把请求的数据保存起来
//数据更新会带动页面重新渲染
yield put({
type: 'save', //reducers中的方法名
payload:{
data: result.data //网络返回的要保留的数据
}
})
}
},
监听路由变化,当进入 /user 页面时,执行 effects 中的 fetch,以从服务端获取用户列表,然后 fetch 中触发 reducers 中的 save 将从服务端获取到的数据保存到 state 中。
effects: {
*fetch(action, { put, call }) {
const users = yield put(fetchUsers, action.data);
yield put({ type: 'save', data: users });
},
},
*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 } });
},
Subscription :语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是 当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
(3)在.webpackrc中通过添加字段来进行自定义antd主题;
CSS Modules配置,