在 React 开发中,随着应用的复杂度增加,如何高效地管理应用状态成为了一个非常重要的问题。为了解决这一问题,很多开发者选择了 Redux,然而 Redux 的学习曲线较陡,且需要配置较多的样板代码。为此,Ant Design 团队基于 Redux 开发了 DVA(Data-View-Action),一个轻量级的、基于 Redux 和 redux-saga 的状态管理框架,旨在简化开发流程,提高开发效率。
DVA 的设计灵感来源于 Model-View-Action(MVA) 模式,与 React 的组件化思想类似。它把数据流、视图渲染、业务逻辑封装成一个个模型,每个模型包含了数据、操作和副作用。DVA 的核心概念包括以下几个部分:
DVA 的核心概念包括以下几个部分:
state
)、同步操作(reducers
)、异步操作(effects
)等。每个 Model 都是独立的,负责某一业务模块的状态和逻辑。dispatch
触发,向 Model 发送指令以更新状态。redux-saga
来处理副作用。DVA 是建立在 Redux 的基础上的,因此它的工作原理与 Redux 类似,具有以下几个关键步骤:
dispatch
派发 action 来更新应用的状态。state
(应用的状态)、reducers
(同步操作)和 effects
(异步操作)。当 dispatch
被触发时,DVA 会调用相应的 reducer
或 effect
。redux-saga
来处理异步操作。在 effects
中,开发者可以通过 yield
来触发异步操作(例如,发起 API 请求),并通过 put
来触发 action,从而更新 state。connect
或 useSelector
获取 state,并将 state 渲染到视图中。任何 state 的更新都会导致视图的重新渲染。大家应该都能理解 redux 的概念,并认可这种数据流的控制可以让应用更可控,以及让逻辑更清晰。但随之而来通常会有这样的疑问:概念太多,并且 reducer, saga, action 都是分离的(分文件)。
这带来的问题是:
还有一些其他的:
而 dva 正是用于解决这些问题。
在 react hooks 上线之后。在 Umi 项目中,我们建议轻量的使用 dva。仅仅在以下几种场景下推荐使用 dva:
要开始使用 DVA,我们首先需要安装它:
npm install dva
当你使用Umi时会内置dva只需要安装插件和配置即可,以全局state为例
// global.ts
import { cloneDeep } from 'lodash'
...
interface userInfo {
avatar: string; // 头像
avatarStatus: string | number; // 头像审核状态。1 正常,2 审核中
avatarPending: string; // 正在审核中的头像
gender: number; // 姓名 未知
countryCode: ''; // 国家地区代码
countryName: ''; // 地区名字
uid: number;// 用户id
uuid: string; // 用户uuid
}
const defaultUserInfo: userInfo = {
avatar: '', // 头像
avatarStatus: '', // 头像状态。1 正常,2 审核中
avatarPending: '', // 正在审核中的头像
gender: -1, // 姓名 未知
countryCode: '', // 国家地区代码
countryName: '', // 地区名字
uid: 0, // 用户id
uuid: '', // 用户uuid
}
const GlobalStore = {
namespace: 'global',
state:{
userInfo: cloneDeep(defaultUserInfo),
},
effects:{
// 获取用户信息
* getUserInfo ({ callback, errorCallback }, { call, put }) {
const res: userInfo = yield call(getUserInfo)
if (!res.code) {
yield put({
type: 'setData',
payload: {
accessToken: '',
userInfo: defaultUserInfo,
isLogin: false,
},
})
if (typeof errorCallback === 'function') {
errorCallback()
}
window.location.href = '/'
return
}
...
if (typeof callback === 'function') {
callback(res.data)
}
},
},
reducers:{
setData(state,{payload}){
return {
...state,
...payload,
}
}
}
}
import { connect, FormattedMessage, useIntl } from 'umi'
...
const NavBar = (props)=>{
const {
global,
dispatch
} = props
const { userInfo } = global
const getUserProfile = (uuid?: string) => {
if (!uuid) {
return
}
dispatch({
type: 'userInfo/getUserInfo',
payload: {
visible: true,
uuid,
},
})
}
...
}
...
export default connect((state) =>({
global:state.global
}))(NavBar )