新入职的公司使用dva框架,之前没有接触过,所以一边学习,一边工作。记一下DVA框架的几个基本概念。
首先放一下DVA框架数据流向:
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过dispatch发起一个action,如果是同步的行为,会直接通过reducer改变state,如果是异步的行为(副作用)会先触发effects然后流向reducer,最终改变state(一般是从服务器获取数据,拿到数据后触发effects),然后通过connect(一个函数,绑定state到view)渲染组件。所以在DVA中,数据流非常清晰简明,并且思路基本跟开源社区保持一致。
DVA的几个概念
Models组成:namespace,state,reducer,effect, subscription
{
namespace: 'count',
state: 0,
reducers: {
add(state) { return state + 1 },
},
effects: {
*addAfter1Second(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'add' });
},
}
},
subscription{
}
State
表示该model当前的状态。数据保存在这里,直接决定了视图层的输出。
State表示Model的状态数据,通常表现为一个js对象(当然可以是任何值);操作的时候每次都要当做不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证State的独立性,便于测试和追踪变化。
state:{
namespace:'businessApprove',
nodeList:[],
checkList:[]
}
在DVA中,你可以通过DVA的实例属性_store来看到顶部的state数据,但是通常很少用到:
const app = dva();
console.log(app._store);//顶部的state数据
Action
一个对象,描述事件,可以是同步,也可以是异步。
Action是一个普通js对象,他是改变State的唯一途径。无论是从UI事件,网络回调,还是WebSocket等数据源说获得的数据,最终都会通过dispatch函数调用一个action,从而改变对应的数据。action必须带有type属性指明具体的行为,其他字段可以自定义。如果要发起一个action需要使用dispatch函数,需要注意的是dispatch是在组件connect Models以后通过props传入的,通过connect将model中的元素作为props的方式传递给component。
dispatch({
type:'changeData',
payload:{
list:[],
}
})
export default connect()(BusinessApprove)
connect
通过connect将module中的元素作为props的方式传递给component。
export default connect(({ businessApprove, loading }) => ({ businessApprove, loading }))(BusinessApprove)
dispatch函数
dispatch函数是一个用于触发action的函数,action是改变state的唯一途径,但是他只是描述了一个行为,而dispatch可以看做是触发这个行为的方式,而reducer则是描述如何改变数据的。
在DVA中,connect Model的组件通过props可以访问到dispatch,可以调用Model中的reducer或者effects,常见的形式如:
dispatch({
type:`${namespace}/changeData`,
//如果在model外调用,需要添加namespace,(`${namespace}`)
payload: {//需要传递的信息
list: [],
}
})
reducer
type Reducer<S,A> = (state:S,action:A) =>S
reducer更新数据,Action处理器,处理同步动作,用来算出最新的state。
reducer函数接受两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。reducer的概念来自于函数式编程。在DVA中,reducer聚合累积的结果是当前model的state对象。通过action中传入的值,与当前reducer中的值进行运算,获得新的值(也就是新的state)。需要注意的是,reducer必须是纯函数,所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且,每一次的计算都应该使用immutable data,这种特性简单理解就是每次操作都是返回一个全新的数据(独立、纯净),所以重热载和时间旅行这些功能才能使用。
reducers: {
changeData (state, { payload }) {
return { ...state, ...payload }
},
...
}
effect
请求数据,action处理器,处理异步动作。底层引入了redux-saga做异步流程控制,采用了generator的相关概念。effect是一个generator函数,内部使用yeild关键字,标识每一步的操作(不管是异步或者同步)。
effect被称为副作用。在我们的应用中,最常见的就是异步操作。它来自于函数编程的概念,之所以叫副作用是因为它使我们的函数变得不纯,同样的输入不一定获得同样的输出。DVA为了控制副作用的操作,底层引入了redux-saga做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effect转换为纯函数。
effects: {
// 查询付款列表
* query ({ payload = {} }, { call, put, select }) {
const data = yield call(query, payload)//发起ajax请求 service内的query异步函数
// const { tableKey } = yield select(({ paymentApprove }) => paymentApprove)//获取model中的state
if (data.success) {
yield put({//提交reducer更改state
type: 'changeData',
payload: {
list: data.data.list,
},
})
} else {
message.error(data.message, 1)
}
},
call和put
DVA提供多个effect函数内部的处理函数,比较常用的是call和put。
call:执行异步函数
put:发出一个action,调用给定的函数,类似于dispatch
select:从全局中取数据
const num = yield select(
state => state.count.num
);
subscription
用于订阅一个数据源,然后根据条件dispatch需要的action。数据源可以是当前的时间,服务器的websocket连接,keyboard的输入,geolocation变化,history路由变化等等。subscription中无法监听state中的数据变化。
model分两类,一是全局model,二是页面model。全局model存在于/src/model/目录,所有页面都可以引用,页面model不能被其他页面所引用。
规则如下:
- src/models/*/.js 为 global model
- src/pages//models//*.js 为 page model
- global model 全量载入,page model 在 production 时按需载入,在 development 时全量载入
- page model 为 page js 所在路径下 models/*/.js 的文件
- page model 会向上查找,比如 page js 为 pages/a/b.js,他的 page model 为 pages/a/b/models//.js + pages/a/models//.js,依次类推
- 约定 model.js 为单文件 model,解决只有一个 model 时不需要建 models 目录的问题,有 model.js 则不去找 models/*/.js
dva01-02.png
如上目录:
● global model 为 src/models/g.js
● /a 的 page model 为 src/pages/a/models/{a,b,ss/s}.js
● /c 的 page model 为 src/pages/c/model.js
● /c/d 的 page model 为 src/pages/c/model.js, src/pages/c/d/models/d.js