dva 数据流
完整掌握 dva 架构:Model、Effect、Subscription、connect
dva 核心概念
dva = Redux + Redux-Saga + React Router + 最佳实践
Vue 2 开发者注意
如果你熟悉 Vuex,dva 的概念类似但更简洁:
State ≈ Vuex State,
Reducers ≈ Vuex Mutations,
Effects ≈ Vuex Actions。
dva 使用 Redux-Saga 处理异步操作。
Model 定义规范
Model 是 dva 的核心,包含状态、同步操作、异步操作
状态数据
同步更新
异步操作
完整 Model 示例
import { Effect, Reducer, Subscription } from 'dva'
// 类型定义
interface UserInfo {
id: string
name: string
email: string
}
interface UserModelState {
currentUser: UserInfo | null
loading: boolean
}
interface UserModelType {
namespace: 'user'
state: UserModelState
effects: {
fetchUser: Effect
}
reducers: {
setUser: Reducer<UserModelState>
clearUser: Reducer<UserModelState>
}
subscriptions: {
setup: Subscription
}
}
const UserModel: UserModelType = {
namespace: 'user',
state: {
currentUser: null,
loading: false
},
effects: {
// Generator 函数处理异步操作
*fetchUser({ payload }, { call, put }) {
const response = yield call(userService.getUser, payload.id)
yield put({
type: 'setUser',
payload: response.data
})
}
},
reducers: {
setUser(state, { payload }) {
return { ...state, currentUser: payload, loading: false }
},
clearUser() {
return { currentUser: null, loading: false }
}
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname }) => {
if (pathname === '/user') {
dispatch({ type: 'fetchUser', payload: { id: '1' } })
}
})
}
}
}
export default UserModel
connect 连接组件
将 Model 的状态和方法连接到 React 组件
import React from 'react'
import { connect } from 'dva'
import { Dispatch } from 'redux'
// 类型定义
interface UserInfo {
id: string
name: string
email: string
}
interface UserPageProps {
currentUser: UserInfo | null
loading: boolean
dispatch: Dispatch
}
// 组件
const UserPage: React.FC<UserPageProps> = ({
currentUser,
loading,
dispatch
}) => {
const handleRefresh = () => {
dispatch({
type: 'user/fetchUser',
payload: { id: '1' }
})
}
if (loading) {
return <div>加载中...</div>
}
if (!currentUser) {
return <div>未找到用户</div>
}
return (
<div>
<h1>{currentUser.name}</h1>
<p>{currentUser.email}</p>
<button onClick={handleRefresh}>
刷新
</button>
</div>
)
}
// 连接到 dva store
const mapStateToProps = (state: any) => ({
currentUser: state.user.currentUser,
loading: state.loading.effects['user/fetchUser']
})
export default connect(mapStateToProps)(UserPage)
connect 参数说明
mapStateToProps: 从 state 中提取数据传给组件
state.loading.effects['user/fetchUser']: dva-loading 插件自动管理的 loading 状态
Effect 详细用法
使用 Generator 函数处理异步操作
Effect 辅助方法
| 方法 | 说明 | 示例 |
|---|---|---|
call |
调用异步函数 | yield call(api.getUser, id) |
put |
触发 reducer | yield put({ type: 'setUser', payload }) |
select |
从 state 中选择数据 | const user = yield select(state => state.user) |
all |
并行执行多个 effect | yield all([call(api1), call(api2)]) |
并行请求示例
effects: {
*fetchDashboard(_, { call, put, all }) {
// 并行请求多个接口
const [users, orders, products] = yield all([
call(api.getUsers),
call(api.getOrders),
call(api.getProducts)
])
yield put({
type: 'setDashboard',
payload: {
users: users.data,
orders: orders.data,
products: products.data
}
})
},
*fetchWithSelect({ payload }, { call, put, select }) {
// 从 state 获取当前数据
const currentUser = yield select(state => state.user.currentUser)
if (currentUser) {
const orders = yield call(api.getUserOrders, currentUser.id)
yield put({
type: 'setOrders',
payload: orders.data
})
}
}
}
Subscription 详解
订阅数据源,自动触发 action
subscriptions: {
// 监听路由变化
setup({ dispatch, history }) {
return history.listen(({ pathname, query }) => {
if (pathname === '/users') {
dispatch({
type: 'fetchUsers',
payload: query
})
}
})
}
}
subscriptions: {
// 监听键盘事件
keyboard({ dispatch }) {
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
dispatch({ type: 'hideModal' })
}
}
document.addEventListener('keydown', handler)
return () => {
document.removeEventListener('keydown', handler)
}
}
}
subscriptions: {
// 定时轮询
poll({ dispatch }) {
const timer = setInterval(() => {
dispatch({ type: 'fetchLatest' })
}, 30000) // 每 30 秒
// 返回清理函数
return () => clearInterval(timer)
}
}
Service 层封装
统一管理 API 请求
import request from '@/utils/request' // 自封装的 axios
// 类型定义
interface UserParams {
id: string
}
interface UserData {
id: string
name: string
email: string
}
// 获取用户信息
export async function getUser(
params: UserParams
): Promise<UserData> {
return request.get(`/api/users/${params.id}`)
}
// 更新用户信息
export async function updateUser(
id: string,
data: Partial<UserData>
) {
return request.put(`/api/users/${id}`, data)
}
// 删除用户
export async function deleteUser(id: string) {
return request.delete(`/api/users/${id}`)
}
dva-loading 插件
自动管理 loading 状态
自动管理的 loading 状态
dva-loading 插件会自动为每个 effect 生成 loading 状态,无需手动管理。
// 在 connect 中使用
const mapStateToProps = (state: any) => ({
// 整个 model 的 loading
userLoading: state.loading.models.user,
// 单个 effect 的 loading
fetchLoading: state.loading.effects['user/fetchUser'],
// 全局 loading
globalLoading: state.loading.global
})