Dva 快速入门(基于 Redux + Redux-Saga)

kkcode
kkcode
2026-08-25阅读 27

一、Dva 是什么

Dva = React + Redux + Redux-Saga + React-Router + Fetch 的轻量上层封装
核心目标:简化 Redux 样板代码,把 reducer、saga、action、state 收拢到 model,不用手动写 action type、connect、saga 监听模板

技术栈底层:

  • Redux:全局状态容器(管理 state + reducer)
  • Redux-Saga:处理异步逻辑(请求接口、定时器、复杂副作用,替代 redux-thunk)

二、核心概念(重中之重)

1. Model(dva 最小单元)

所有状态、同步更新、异步请求全部写在 model 中,结构固定:

{
  namespace: 'user', // ✅唯一命名空间,state挂载在全局state.user
  state: {},         // 初始状态
  reducers: {},      // ✅同步更新state(纯函数,不能写异步)
  effects: {},        // ✅异步逻辑(saga封装,接口请求、延时)
  subscriptions: {}  // 订阅(路由监听、全局事件、初始化执行)
}

简单区分 reducer / effect

  1. reducer:同步
    接收旧 state + payload,返回新 state,禁止异步、不能请求接口
  2. effect:异步(saga)
    处理接口请求、定时器;不能直接改 state,需要调用 put 触发 reducer 更新状态

2. 内置 saga 常用 API(effects 内部使用)

// put:分发 action,触发 reducer
yield put({ type: 'save', payload: data })

// call:执行异步函数(请求接口,支持捕获异常)
yield call(api.getUser, params)

// select:读取全局 state
const userInfo = yield select(state => state.user)

// takeLatest:自动取消上一次未完成请求(搜索防抖常用,dva默认)
// takeEvery:执行每一次请求

3. 数据流完整流程

组件 dispatch(action) 
    → effects(异步请求) 
    → put(action) 
    → reducers(更新state) 
    → 组件自动获取最新state渲染

三、最简完整示例

1. 安装

# dva 官方包
npm install dva --save
# or yarn
yarn add dva

2. 创建 dva 实例 & 挂载应用(index.js)

import { createBrowserHistory as createHistory } from 'history';
import { create } from 'dva';
import App from './App';

// 创建history
const history = createHistory();
// 创建dva实例
const app = create({ history });

// 注册model
app.model(require('./models/user').default);

// 挂载路由根组件
app.router(() => <App />);

// 启动应用
app.start('#root');

3. 编写 model(models/user.js)

export default {
  namespace: 'user',
  state: {
    info: null,
    loading: false
  },

  // 同步修改状态
  reducers: {
    setLoading(state, { payload }) {
      return { ...state, loading: payload };
    },
    saveUser(state, { payload }) {
      return { ...state, info: payload };
    }
  },

  // 异步副作用 saga
  effects: {
    *fetchUser({ payload }, { call, put }) {
      try {
        yield put({ type: 'setLoading', payload: true });
        // 模拟接口请求
        const res = yield call(
          () => new Promise(resolve => setTimeout(() => resolve({ name: '张三' }), 800))
        );
        // 更新state
        yield put({ type: 'saveUser', payload: res });
      } catch (err) {
        console.error('请求失败', err);
      } finally {
        yield put({ type: 'setLoading', payload: false });
      }
    }
  },

  subscriptions: {
    // 页面初始化自动执行
    setup({ dispatch }) {
      dispatch({ type: 'fetchUser' });
    }
  }
};

4. 组件中使用状态 & 触发 action(类组件 / 函数组件)

方式1:函数组件 + react-redux useSelector / useDispatch(推荐)

import { useSelector, useDispatch } from 'dva';

export default function UserPage() {
  // 读取model中的state
  const { info, loading } = useSelector(state => state.user);
  const dispatch = useDispatch();

  const loadData = () => {
    // 触发effect
    dispatch({ type: 'user/fetchUser', payload: { id: 1 } });
  };

  return (
    <div>
      {loading ? '加载中...' : <div>用户名:{info?.name}</div>}
      <button onClick={loadData}>刷新用户</button>
    </div>
  );
}

方式2:类组件 connect(老项目常用)

import { connect } from 'dva';

function mapStateToProps(state) {
  return {
    user: state.user
  };
}

class User extends React.Component {
  load = () => {
    this.props.dispatch({ type: 'user/fetchUser' });
  };
  render() {
    const { user } = this.props;
    return <button onClick={this.load}>{user.loading ? '加载' : '查询'}</button>;
  }
}
export default connect(mapStateToProps)(User);

四、Dispatch 书写规则

namespace + / + 方法名

// 触发effect
dispatch({ type: 'user/fetchUser', payload: 参数 })
// 直接触发reducer(少量场景使用)
dispatch({ type: 'user/saveUser', payload: { name: '李四' } })

五、常见坑(新手必看)

  1. reducer 必须返回全新对象!不要直接修改原state
// ❌错误
state.info.name = 'xxx'
// ✅正确
return { ...state, info: { ...state.info, name: 'xxx' } }
  1. effects 内不能直接赋值修改state,只能 put 调用 reducer
  2. namespace 不能重名,全局唯一
  3. yield 只能在 function* 生成器函数(effects 方法)中使用,普通函数不能写 yield
  4. 不要滥用全局state:局部状态优先用 useState,跨组件共享再放进 dva model

六、Dva 和原生 Redux 对比

原生ReduxDva
需要手动定义 ActionType 常量直接字符串 namespace/方法名
store、reducer、saga分开多个文件全部收拢在 model
手动配置 saga中间件内置saga,零配置
模板代码极多极简模板

七、扩展学习路线

  1. 熟练 call/put/select
  2. 掌握 takeLatest / takeEvery / race 控制请求竞态
  3. 学会 subscription 监听路由变化(登录鉴权)
  4. 封装统一接口请求、全局loading中间件
评论数量:0