在umi框架,react函数式组件中使用dva管理数据

3,889 阅读3分钟

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中。