Dva是什么
dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。( Inspired by elm and choo. )
- 框架,而非类库
- 基于redux, react-router, redux-soga的轻量级封装
- 借鉴elm的概念, Reducer, Effect和Subscription
- ...
可以说,dva是基于react+redux最佳实践上实现的封装方案,简化了redux和redux-saga使用上的诸多繁琐操作。
文件结构
官方推荐的:
├── /mock/ # 数据mock的接口文件
├── /src/ # 项目源码目录
│ ├── /components/ # 项目组件
│ ├── /routes/ # 路由组件(页面维度)
│ ├── /models/ # 数据模型
│ ├── /services/ # 数据接口
│ ├── /utils/ # 工具函数
│ ├── route.js # 路由配置
│ ├── index.js # 入口文件
│ ├── index.less
│ └── index.html
├── package.json # 定义依赖的pkg文件
└── proxy.config.js # 数据mock配置文件
初体验
一个小Demo
5个API
app = dva(Opts)
app.use(Hooks)
app.models(ModelObject)
app.router(Function)
app.start([HTMLElement])
8个概念
- State
- Action
- Model
- Reducer
- Effect
- Subscription
- Router
- RouteComponent
数据流向
数据的改变发生通常是通过:
- 用户交互行为(用户点击按钮等)
- 浏览器行为(如路由跳转等)触发的
当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State 。
所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致。如上图。
相关概念与理解
定义model
namespace: 'monthCard',
state: {
list: [],
total: 0,
editData: {},
loading: false
},
subscriptions: {
setup({ dispatch, history}){
}
},
effects: {},
reducers: {}
State是整个应用的数据层。应用的state被存储在一个object tree中。应用的初始state在model中定义,也就是说,由model state组成全局state。操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。
namespace
是model state在全局state中所用到的key。
这里的Model非MVC中的M,是用于把数据相关的逻辑聚合到一起。
完成component
const monthCardPrice = ({
monthCard,
monthCard: {
list
},
diapacth
}) => {
const queryList = () => {
dispatch({
type: `monthCard/query`,
payload: {}//需要传递的数据
})
}
return (
<div>
<button onclick={queryList}>查询</button>
</div>
)
}
const mapStateToProps = ({
monthCard,
global
}) => {
return {
monthCard,
global
}
}
export default connect(mapStateToProps)(monthCardPrice)
RouteComponent 表示 Router 里匹配路径的 Component,通常会绑定 model 的数据
Presentational Component是独立的纯粹的,例如ant.design UI组件的react实现,每个组件跟业务数据并没有耦合关系,只是完成自己独立的任务,需要的数据通过 props 传递进来,需要操作的行为通过接口暴露出去。 而 Container Component 更像是状态管理器,它表现为一个容器,订阅子组件需要的数据,组织子组件的交互逻辑和展示。
所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在/routes/目录下,而/components/目录下则是纯组件(Presentational Components)。
dva架构里component中基本不需要用到state。
绑定数据
这里利用es6结构赋值通过props将参数传入组件。monthCard
对应model上的state,通过connect
来绑定绑定model state。这里connect来自react-redux。意味着Component里可以拿到Model中定义的数据,Model中也能接收到Component里dispatch的action。实现了Model和Component的连接。Action
Action表示操作事件,可以是同步,也可以是异步。Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。
dispatch 函数,通过 type 属性指定对应的 actions 类型,而这个类型名在 reducers(effects)会一一对应,从而知道该去调用哪一个 reducers(effects),除了 type 以外,其它对象中的参数随意定义,都可以在对应的 reducers(effects)中获取,从而实现消息传递,将最新的数据传递过去更新 model 的数据(state)
注意:Action在model自身模型以外定义时需要加model的namespace前缀, 在model中定义不需要加。
- 添加样式:
CSS Modules会给组件的className加上hash字符串,来保证className仅对引用了样式的组件有效,如styles.normal可能会输出为normal___39QwY。
className的输出格式可以通过webpack.config进行修改。
更新state
namespace: 'monthCard',
state: {
list: [],
total: 0,
editData: {},
loading: false
},
subscriptions: {},
effects: {},
reducers: {
+ updateState(state, action){
+ return {
+ ...state,
+ ...action.payload
+ }
+ }
}
reducer 是唯一可以更新 state 的地方,这个唯一性让我们的 App 更具可预测性,所有的数据修改都有据可查。reducer 是 pure function,他接收参数 state 和 action,返回新的 state,通过语句表达即 (state, action) => newState。该函数把一个集合归并成一个单值。
Reducer 的概念来自于是函数式编程,在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。
注意:Reducer函数必须是纯函数。
Effects异步处理
+ //异步请求
//request 是我们封装的一个网络请求库
+ async function queryFromService(data) {
+ return request("queryFromApi", {
+ data,
+ method: "post",
+ dataType: "payload"
+ })
+ }
namespace: 'monthCard',
state: {
list: [],
total: 0,
editData: {},
loading: false
},
subscriptions: {},
effects: {
+ * query({ payload }, { call, put }){
+ yield put ({
+ type: `updateState`,
+ payload: {
+ loading: true
+ }
+ })
+
+ const { data } = yield call (queryFromService, parse(payload))
+
+ if(data){
+ yield put({
+ type: 'querySuccess',
+ payload: {
+ loading: false,
+ list: data.data,
+ editData: data.data,
+ total: data.recordsTotal
+ }
+ })
+ } else {
+ yield put({
+ type: 'querySuccess',
+ payload: {
+ data: {},
+ loading: false
+ }
+ })
+ }
+ },
+ * create(){},
+ * 'delete'(){},//delete是关键字
+ * update(){}
},
reducers: {
updateState(state, action){
return {
...state,
...action.payload
}
},
+ querySuccess(state, action){
+ if(action.payload.data){
+ const {
+ data: {
+ status,
+ message
+ }
+ } = action.payload
+
+ if(1 == status){
+ //
+ }else {
+ Message.error(message)
+ }
+ }
+
+ return {
+ ...state
+ }
+ }
}
当数据需要从服务器获取时,需要发起异步请求,请求到数据之后,通过调用 Reducers更新数据到全局state。dva 通过对 model 增加 effects 属性来处理 side effect(异步任务),这是基于 redux-saga 实现的,语法为 generator。Generator 返回的是迭代器,通过 yield 关键字实现暂停功能。Redux-saga 中文文档。
* query(action, {call, put, select}){}
表示一个worker Saga,监听所有的query
action,并触发一个Api调用以获取服务器数据。当每个query
action被发起时调用 call 和 put 都是 redux-saga 的 effects,call 表示调用异步函数,put 表示 dispatch action,其他的还有 select, take, fork, cancel 等,select 则可以用来访问其它 model。格式:*(action, effects) => void
在Effects里,Generator函数通过yield命令将异步操作同步化,无论是yield 亦或是 async 目的只有一个: 让异步编写跟同步一样 ,从而能够很好的控制执行流程。
订阅数据源
subscriptions: {
setup(( dispatch, history )){
history.listen(({ pathname, query }) => {
if(pathToRegexp(`/y/monthCard/list`).test(pathname)) {
dispatch({
type:`query`
})
}
})
}
}
Subscriptions 表示订阅,用于订阅一个数据源,然后按需 dispatch action。格式为 ({ dispatch, history }) => unsubscribe 。比如:当用户进入 /y/monthCard/list 页面时,触发 action query 加载数据。
如果 url 规则比较复杂,比如 /users/:userId/search,那么匹配和 userId 的获取都会比较麻烦。这是推荐用 path-to-regexp 简化这部分逻辑。
定义路由
路由决定进入url渲染哪些Component。history 默认是 hashHistory 并且带有 _k
参数,可以换成 browserHistory,也可以通过配置去掉 _k
参数。
工具
dva使用总结
dva将所有与数据操作相关的逻辑集中放在一个地方处理和维护,在数据跟业务状态交互比较紧密的场景下,会使我们的代码更加清晰可控。尤其适用于数据跟业务状态关联性极强的企业级后台信息管理系统。
对于一个企业级后台管理系统,由于要进行大量的数据操作,在设计model时将不同类型的业务需求数据操作分开处理,便于维护。
项目的开发流程一般是从设计model state开始进行抽象数据,完成component后,将组件和model建立关联,通过dispatch一个action,在reducer中更新数据完成数据同步处理;当需要从服务器获取数据时,通过Effects数据异步处理,然后调用Reducer更新全局state。是一个单向的数据流动过程。解决了redux中代码分散和重写问题,总之,Dva:Build redux application easier and better。
学习资料
官方地址
Redux-saga 中文文档
dva-knowledgemap
dva: react application arch in ant financial