数据流向
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由器跳转等)触发的,当此类行为会改变数据的时候可以通过dispatch
发起一个action时:
- 如果是同步行为或直接通过
Reducers
改变State
- 若是异步行为(副作用)会触发
Effects
然后流向Reducers
,最终改变State
所以,在dva中数据流非常清晰简明。
Models
State
type State = any
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事件、网络回调,还是WebScoket等数据源获得的数据,最终都会通过dispatch函数调用一个action,从而改变对应的数据。
action必须带有type
属性指明具体的行为,其他字段可以自定义,若要发起一个action需要使用dispatch
函数;需要注意的是dispatch
是在组件connect Models以后,通过props传入的。
dispatch({
type:'add',
});
dispatch函数
type dispatch = (a:Action) => Action
dispatching function是一个用于触发action的函数,action是改变State的唯一途径,但是它只描述了一个行为,而dispatch可以看作是触发这个行为的方式,而Reducer则是描述如何改变数据的。
在dva中,connect Model的组件通过props可以访问到dispatch,可以调用Model中的Reducer或者Effects,常见的形式:
dispatch({
type:'use/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对象,通过action中传入的值,与当前reducers中的值进行运算获得新的值(也就是新的state)。需要注意的是Reducer必须是纯函数,所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且,每一次的计算都应该使用immutable data,这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。
Effect
Effect被称为副作用,在react应用中最常见的的就是异步操作。它来源于函数编程的概念,之所以称之为副作用是应为它试得我们的函数变得不纯,同样的输入不一定获得同样的输出。
dva为了控制副作用的操作,在底层引入了redux-sagas做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。
Subscription
Subscriptions是一种从源获取数据的方法,来自于elm。
Subscriptions语义是订阅,用于订阅一个数据源,然后根据条件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}>
<Router path="/" component={HomePage} />
</Router>
});
Route Components
在组件设计方法中,我们提到过 Container Components,在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。
所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在/routes/目录下,而/components/目录下则是纯组件(Presentational Components)。