dva通过model的概念来管理一个领域的模型,包含同步更新的state的reducer,处理异步逻辑的effects等。effects异步处理数据之后的数据,最终也是流向到reducer再去更改state。所以,在dva中,这种单向的数据流转,非常清晰。
在使用dva之前,先来搞清楚几个概念:state,reducer,effect,dispatch,action,以及它们之间的关系。
dva的一些基础知识点
1、state
state是model的状态数据,基本上是需要在业务组件中使用到的那些数据。更改state有个需要注意的地方,为了保证数据变化时候的可控性,每一次变更state都是返回一个全新的对象,而不能使用引用关系并更改state里面的某个值。因此,在reducer的方法中,每一个方法都是通过return的方式,返回一个全新的state对象。
updateStore:(state), action) => {
console.log(action.type) // '${namespance}/updateStore'
return {
...state,
...action.payload
}
}
2、reducer
type Reducer<S, A> = (state: S, action: A) => S
dva官网使用这种方式标明了reducer的参数,以及它的类型,插一句题外话,这种通过typescript的类型来作为参数的文档,貌似现在比较流行的方式。一些同事在看源码的时候,也喜欢跳转到ts的类型来查看参数。
reducer接受两个参数,state是当前运行时的model中的state,action中的payload携带了需要修改的数据,reducer通过纯函数的方式,返回一个完全新的state。
3、effect
因为一些异步操作传入同样的值可能返回的是不同的结果,因此,将异步请求的一些操作都统一放到了effect中来管理。effect在这里面就是副作用的意思。至于dva底层是如何将effect副作用转为纯函数的原因,我还没理解到。大概就是用了某些东西结合generator使它转变成了纯函数。因此,effects的方法前,一般都使用*结合yield的方式。
effects: {
*someEffect({ payload }, { call, put, select }) {
yield put({
type: 'updateStore',
payload: {
stateA: '新的值'
}
})
}}
4、action
action是一个对象,作为参数传递给dispatch或者put函数,它是唯一可以改变state的途径。通过action来标明当前的行为想要改变具体的model以及state的哪些内容,在我的项目中,我使用了以下命名的方式来更新store:
// 在model外面,通常是你的业务组件中,需要添加namespace命名空间来标明你想要更改哪个model中的state
dispatch({
type: '${namespace}/updateStore',
payload: {
stateA: '新的值'
}
})
// 在model内部,如effect中,通过put的方式
effects: {
*someEffect({ payload }, { call, put, select }) {// call, put, select这些参数的含义,还要查一下文档
yield put({
type: 'updateStore',
payload: {
stateA: '新的值'
}
})
}}
action中必须有type标明具体的行为。namespace对应具体要更改的model,‘updateStore‘对应的就是model文件中的reducer下的具体的行为。
5、dispatch
dispatch跟action通常是一起使用,action标明具体行为以及通过它会将state变成一个什么样的新的state,在业务组件中,靠dispatch方法来触发这个行为。
6、额外介绍个知识点,subscritions。
这个在官网里面描述的内容很少,之前用vue的时候也没有看到过类似的设计,所以官网看的不太懂,看完给人一种云里雾里的感觉,经过一通百度,看到大神的介绍,终于搞明白这个东西是用来干什么的了。先上一段代码看下,结合注释看完你就恍然大悟了,看不懂再看后面的文字。
subscriptions: {
onClick({dispatch}) => { // 这个onClick方法的命名是没有意义的,随意写,换成onClickkkk效果也是一样.当监听有变化的时候,会一次执行这里的代码
dispatch({
type: 'click',
payload: {} // 至于payload,你写不写看需求。
})
},
onHistoryChange({dispatch, history}) => {
history.listen((location) => {
console.log(location)
// xxx代码,你想dispatch或者啥都行
// xxx代码,你想dispatch或者啥都行
})
}
}
看到这里,再回去看官网的例子,是不是一下子就明白了?
官网摘录: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'}) });
},
}
});
在umi项目中使用dva的方式如下:
umi创建的项目的src/models目录下,定义一个global.ts文件
global.ts对象export一个对象
// ts ,如果没有引入ts可以忽略
export type GlobalModelState = { projectInfo: Object};
// ts ,如果没有引入ts可以忽略
export type GlobalModelType = {
namespace: 'global';
state: GlobalModelState;
effects: {};
reducers: {};
};
const GlobalModel: GlobalModelType = {
namespace: 'global',
state: {
projectInfo: {}
},
effects: {},
reducers: {
updateStore: (state: any, { payload }: { payload: any }) => {
return {
...state,
...payload
}
}
},
};
注:updateStore接受的两个参数,一个是state,代表当前model中的state,另一个参数是action,也就是在业务组件中通过如下方式dispatch方法中的参数,包含一个type,一个payload。updateStore: (state: any, { payload }: { payload: any })此处涉及到了对象结构以及ts的类型定义,如果不是ts,直接updateStore(state, action) =>{} 即可
const mapDispatchToProps = (dispatch: any) => {
return {
updateProjectInfo: (projectInfo: any) => {
dispatch({
type: `${namespace}/updateStore`,
payload: {
projectInfo
}
})
}
}}
dva通过connect方法将model跟组件串联起来,使用方式如下:
// 业务组件.jsx
import { connect } from 'dva'
import React, {useState} from 'react'
const xxComp:React.FC = (props) => {
const { dispatch } = props
//
//
const doSomething = ()=> {
dispatch({
type: '${'global'}/updateStore' // 这里的global对应的是你要更新的namespace对应的model
payload: {
projectInfo: '新的projectInfo'
}
})
}
}
export default connect()(xxComp)
注:当通过conect将model与组件连接起来后,dispatch会自动注入到该组件的props中。