🏗️ dva 核心概念

dva = Redux + Redux-Saga + React Router + 最佳实践

View (React 组件)
connect() - 连接 Model 和 View
Model (数据模型: State + Reducers + Effects)
Service (请求层: axios)
ℹ️

Vue 2 开发者注意

如果你熟悉 Vuex,dva 的概念类似但更简洁: State ≈ Vuex State, Reducers ≈ Vuex Mutations, Effects ≈ Vuex Actions。 dva 使用 Redux-Saga 处理异步操作。

📦 Model 定义规范

Model 是 dva 的核心,包含状态、同步操作、异步操作

State

状态数据

Reducers

同步更新

Effects

异步操作

完整 Model 示例

models/user.ts
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 组件

pages/UserPage.tsx
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 请求

services/user.ts
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 状态,无需手动管理。

使用 dva-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
})
← 组件开发迁移 路由迁移 →