一、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
- reducer:同步
接收旧 state + payload,返回新 state,禁止异步、不能请求接口 - 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: '李四' } })
五、常见坑(新手必看)
- reducer 必须返回全新对象!不要直接修改原state
// ❌错误
state.info.name = 'xxx'
// ✅正确
return { ...state, info: { ...state.info, name: 'xxx' } }
- effects 内不能直接赋值修改state,只能
put调用 reducer - namespace 不能重名,全局唯一
yield只能在function*生成器函数(effects 方法)中使用,普通函数不能写 yield- 不要滥用全局state:局部状态优先用
useState,跨组件共享再放进 dva model
六、Dva 和原生 Redux 对比
| 原生Redux | Dva |
|---|---|
| 需要手动定义 ActionType 常量 | 直接字符串 namespace/方法名 |
| store、reducer、saga分开多个文件 | 全部收拢在 model |
| 手动配置 saga中间件 | 内置saga,零配置 |
| 模板代码极多 | 极简模板 |
七、扩展学习路线
- 熟练
call/put/select - 掌握
takeLatest / takeEvery / race控制请求竞态 - 学会
subscription监听路由变化(登录鉴权) - 封装统一接口请求、全局loading中间件
