最近在做react的项目先是阿里云的oss,再是支付宝的ant design,再次要吐槽一些ant design的upload组件对于oss上传的支持真的时糟心。之后项目的状态管理要用到dva,也是今天的爬坑对象。
why dva and what's dva
当我们在学习react的时候都是react+redux的模式开始,但是redux的学习成本有点高。redux的核心时store、action和reducer。现在处理异步要用到react-saga。在写redux时需要在reducer、sage、action、action之间来回切换,后期state难以维护,不便于组织业务模型,还有就是sage书写太复杂,每监听一个 action 都需要走 fork -> watcher -> worker 的流程。

dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。( Inspired by elm and choo. )
dva 是 framework,不是 library,类似 emberjs,会很明确地告诉你每个部件应该怎么写,这对于团队而言,会更可控。另外,除了 react 和 react-dom 是 peerDependencies 以外,dva 封装了所有其他依赖。
dva 实现上尽量不创建新语法,而是用依赖库本身的语法,比如 router 的定义还是用 react-router 的 JSX 语法的方式(dynamic config 是性能的考虑层面,之后会支持)。

特性
- 易学易用: 仅有6个api,对redux用户友好
- elm概念: 通过
reducers,effects,subscriptions组织model - 支持mobile和react-native: 跨平台
- 支持 HMR:目前基于
babel-plugin-dva-hmr支持 components、routers和models的HMR - 动态加载Model和路由:按需加载加快访问速度
- 插件机制
- 完善的语法分析库
dva-ast: dva-cli - 支持TypeScript
dva 的五部曲
import './index.html';
import './index.css';
import dva from 'dva';
// 1. Initialize
const app = dva();
// 2. Plugins - 该项为选择项
//app.use({});
// 3. Model 的注册
//app.model(require('./models/example'));
// 4. 配置 Router
app.router(require('./router'));
// 5. Start
app.start('#root');
dva文档还有一步
const App = connect(mapStateToProps)(Component)
核心model
/** Created by guangqiang on 2017/12/17. */
import queryString from 'query-string'
import * as todoService from '../services/todo'
export default {
namespace: 'todo',
state: {
list: []
},
reducers: {
save(state, { payload: { list } }) {
return { ...state, list }
}
},
effects: {
*addTodo({ payload: value }, { call, put, select }) {
// 模拟网络请求
const data = yield call(todoService.query, value)
console.log(data)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
const tempObj = {}
tempObj.title = value
tempObj.id = list.length
tempObj.finished = false
list.push(tempObj)
yield put({ type: 'save', payload: { list }})
},
*toggle({ payload: index }, { call, put, select }) {
// 模拟网络请求
const data = yield call(todoService.query, index)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
let obj = list[index]
obj.finished = !obj.finished
yield put({ type: 'save', payload: { list } })
},
*delete({ payload: index }, { call, put, select }) {
const data = yield call(todoService.query, index)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
list.splice(index, 1)
yield put({ type: 'save', payload: { list } })
},
*modify({ payload: { value, index } }, { call, put, select }) {
const data = yield call(todoService.query, value)
let tempList = yield select(state => state.todo.list)
let list = []
list = list.concat(tempList)
let obj = list[index]
obj.title = value
yield put({ type: 'save', payload: { list } })
}
},
subscriptions: {
setup({ dispatch, history }) {
// 监听路由的变化,请求页面数据
return history.listen(({ pathname, search }) => {
const query = queryString.parse(search)
let list = []
if (pathname === 'todoList') {
dispatch({ type: 'save', payload: {list} })
}
})
}
}
}
注册
在router.js中注册namespace
import React from 'react'
import PropTypes from 'prop-types'
import { Router, Route, IndexRoute, IndexRedirect } from 'dva/router'
import App from './routes/App'
export default function ({ history, app }) {
reginsterModel(app, require('./models/todo'))
return (
<Router history = {history}>
<Route path="index"></Route>
</Router>
)
}
在组件中使用
import React, {PureComponent} from 'react'
import {connect} from 'dva'
import {routerRedux} from 'dva/router'
import {Button} from 'antd'
const namespace = 'todo'
function delete () {
dispatch({
type: `${namespace}/delete`
})
}
class Index extends PureComponent {
static PropTypes = {
todo: PropTypes.object.isRequired,
}
render() {
return(
<Button onClick={this.delete()}>click</Button>
)
}
}
function mapStateToProps({todo}){
return {todo}
}
export default connect(mapStateToProps) (Index)
还有更加详细的用法请参考 dev文档