1、dva 是什么?
dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,所有代码不到 100 行。( Inspired by elm and choo. )javascript
dva 是 framework,不是 library,相似 emberjs,会很明确地告诉你每一个部件应该怎么写,这对于团队而言,会更可控。另外,除了 react 和 react-dom 是 peerDependencies 之外,dva 封装了全部其余依赖。html
dva 实现上尽可能不建立新语法,而是用依赖库自己的语法,好比 router 的定义仍是用 react-router 的 JSX 语法的方式(dynamic config 是性能的考虑层面,以后会支持)。前端
他最核心的是提供了 app.model
方法,用于把 reducer, initialState, action, saga 封装到一块儿,好比:java
app.model({ namespace: 'products', state: { list: [], loading: false, }, subscriptions: [ function(dispatch) { dispatch({type: 'products/query'}); }, ], effects: { ['products/query']: function*() { yield call(delay(800)); yield put({ type: 'products/query/success', payload: ['ant-tool', 'roof'], }); }, }, reducers: { ['products/query'](state) { return { ...state, loading: true, }; }, ['products/query/success'](state, { payload }) { return { ...state, loading: false, list: payload }; }, }, });
在有 dva 以前,咱们一般会建立 sagas/products.js
, reducers/products.js
和 actions/products.js
,而后在这些文件之间来回切换。react
介绍下这些 model 的 key :(假设你已经熟悉了 redux, redux-saga 这一套应用架构)git
- namespace - 对应 reducer 在 combine 到 rootReducer 时的 key 值
- state - 对应 reducer 的 initialState
- subscription - elm@0.17 的新概念,在 dom ready 后执行,这里不展开解释,详见:A Farewell to FRP
- effects - 对应 saga,并简化了使用
- reducers
特性:github
- 易学易用:仅有 6 个 api,对 redux 用户尤为友好
- elm 概念:经过
reducers
,effects
和subscriptions
组织 model - 支持 mobile 和 react-native:跨平台 (react-native 例子)
- 支持 HMR:目前基于 babel-plugin-dva-hmr 支持 components、routes 和 models 的 HMR
- 动态加载 Model 和路由:按需加载加快访问速度 (例子)
- 插件机制:好比 dva-loading 能够自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
- 完善的语法分析库 dva-ast:dva-cli 基于此实现了智能建立 model, router 等
- 支持 TypeScript:经过 d.ts (例子)
2、dva 的8个概念
数据流向
数据的改变发生一般是经过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候能够经过 dispatch
发起一个 action,若是是同步行为会直接经过 Reducers
改变 State
,若是是异步行为(反作用)会先触发 Effects
而后流向 Reducers
最终改变 State
,因此在 dva 中,数据流向很是清晰简明,而且思路基本跟开源社区保持一致(也是来自于开源社区)。web
Models
State
type State = any
typescript
State 表示 Model 的状态数据,一般表现为一个 javascript 对象(固然它能够是任何值);操做的时候每次都要看成不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。编程
在 dva 中你能够经过 dva 的实例属性 _store
看到顶部的 state 数据,可是一般你不多会用到:
const app = dva(); console.log(app._store); // 顶部的 state 数据
Action
type AsyncAction = any
Action 是一个普通 javascript 对象,它是改变 State 的惟一途径。不管是从 UI 事件、网络回调,仍是 WebSocket 等数据源所得到的数据,最终都会经过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type
属性指明具体的行为,其它字段能够自定义,若是要发起一个 action 须要使用 dispatch
函数;须要注意的是 dispatch
是在组件 connect Models之后,经过 props 传入的。
dispatch({ type: 'add', });
dispatch 函数
type dispatch = (a: Action) => Action
dispatching function 是一个用于触发 action 的函数,action 是改变 State 的惟一途径,可是它只描述了一个行为,而 dipatch 能够看做是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
在 dva 中,connect Model 的组件经过 props 能够访问到 dispatch,能够调用 Model 中的 Reducer 或者 Effects,常见的形式如:
dispatch({ type: 'user/add', // 若是在 model 外调用,须要添加 namespace payload: {}, // 须要传递的信息 });
Reducer
type Reducer<S, A> = (state: S, action: A) => S
Reducer(也称为 reducing function)函数接受两个参数:以前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。
Reducer 的概念来自因而函数式编程,不少语言中都有 reduce API。如在 javascript 中:
[{x:1},{y:2},{z:3}].reduce(function(prev, next){ return Object.assign(prev, next); }) //return {x:1, y:2, z:3}
在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。经过 actions 中传入的值,与当前 reducers 中的值进行运算得到新的值(也就是新的 state)。须要注意的是 Reducer 必须是纯函数,因此一样的输入必然获得一样的输出,它们不该该产生任何反作用。而且,每一次的计算都应该使用immutable data,这种特性简单理解就是每次操做都是返回一个全新的数据(独立,纯净),因此热重载和时间旅行这些功能才可以使用。
Effect
Effect 被称为反作用,在咱们的应用中,最多见的就是异步操做。它来自于函数编程的概念,之因此叫反作用是由于它使得咱们的函数变得不纯,一样的输入不必定得到一样的输出。
dva 为了控制反作用的操做,底层引入了redux-sagas作异步流程控制,因为采用了generator的相关概念,因此将异步转成同步写法,从而将effects转为纯函数。至于为何咱们这么纠结于 纯函数,若是你想了解更多能够阅读Mostly adequate guide to FP,或者它的中文译本JS函数式编程指南。
Subscription
Subscriptions 是一种从 源 获取数据的方法,它来自于 elm。
Subscription 语义是订阅,用于订阅一个数据源,而后根据条件 dispatch 须要的 action。数据源能够是当前的时间、服务器的 websocket 链接、keyboard 输入、geolocation 变化、history 路由变化等等。
import key from 'keymaster'; ... app.model({ namespace: 'count', subscriptions: { keyEvent(dispatch) { key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) }); }, } });
Router
这里的路由一般指的是前端路由,因为咱们的应用如今一般是单页应用,因此须要前端代码来控制路由逻辑,经过浏览器提供的 History API 能够监听浏览器url的变化,从而控制路由相关操做。
dva 实例提供了 router 方法来控制路由,使用的是react-router。
import { Router, Route } from 'dva/router'; app.router(({history}) => <Router history={history}> <Route path="/" component={HomePage} /> </Router> );
Route Components
在组件设计方法中,咱们提到过 Container Components,在 dva 中咱们一般将其约束为 Route Components,由于在 dva 中咱们一般以页面维度来设计 Container Components。
因此在 dva 中,一般须要 connect Model的组件都是 Route Components,组织在/routes/
目录下,而/components/
目录下则是纯组件(Presentational Components)。
参考引伸
原文地址:https://github.com/dvajs/dva/blob/master/docs/Concepts_zh-CN.md