目录
2.4 状态管理 models_reducer(同步)(以计数器为例)
2.6 订阅监听(subscriptions)(以检查登录状态为例)
DvaJS 简介
DvaJS 是一个基于 React 和 Redux 的轻量级前端框架,由支付宝团队开发并开源。它整合了 Redux、Redux-Saga、React-Router 等库,并提供了更简洁的开发模式,适用于构建复杂的中后台管理系统。
一、核心特性
1.1 简化 Redux 开发
内置 Redux 状态管理,但通过
model
概念简化了action
、reducer
、effect
(异步逻辑)的定义。不再需要手动编写
action types
和reducer
的switch-case
。
1.2 集成 Redux-Saga
使用 Saga 处理异步逻辑(如 API 请求),支持
async/await
风格。提供
effects
语法糖,简化put
、call
、take
等 Saga 操作。
1.3 内置 React-Router
支持动态路由配置,简化路由管理。
1.4 Model 驱动开发
应用状态和逻辑按
model
组织,每个model
包含:namespace
(命名空间,类似模块名)state
(初始状态)reducers
(同步更新状态)effects
(异步逻辑)subscriptions
(订阅数据,如监听路由变化)
1.5 插件机制
支持插件扩展(如
dva-loading
自动管理 loading 状态)。
二、基本使用
2.1 安装及创建
通过 npm 安装 dva-cli 并确保版本是 0.9.1 或以上。
// 全局安装
npm install dva-cli -g
// 判断是否安装成功
dva -a
安装完 dva-ci 之后,就可以通过 dva new
创建新应用。
// 创建应用
dva new my-dva
// 进入目录并安装模块
cd my-dva
npm install
运行dva项目
npm start
2.2 目录结构
2.3 路由使用
// src/router.js
import React from "react";
import { Router, Route, Switch } from "dva/router";
import IndexPage from "./routes/IndexPage";
import UserPage from "./routes/UserPage";
function RouterConfig({ history }) {
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={IndexPage} />
<Route path="/user" exact component={UserPage} />
</Switch>
</Router>
);
}
export default RouterConfig;
// src/routes/UserPage.js
import React from "react";
import { Link } from "dva/router";
export default function UserPage(props) {
console.log(props);
const handleClick = () => {
props.history.push("/"); //函数式组件可以使用useNavigate
};
return (
<div>
<div>用户页</div>
<Link to="/">跳转到首页</Link>
<button onClick={handleClick}>点击跳转到首页</button>
</div>
);
}
2.4 状态管理 models_reducer(同步)(以计数器为例)
2.4.1 创建model
// src/model/count.js
export default {
// 命名空间
namespace: "count",
// 状态
state: {
sum: 0,
},
// 同步操作
reducers: {
increment(state, action) {
// 这里不能直接修改state返回,需要拷贝一个新的
let nState = JSON.parse(JSON.stringify(state));
nState.sum += action.data.num;
return nState;
},
decrement(state, action) {
// 这里不能直接修改state返回,需要拷贝一个新的
let nState = JSON.parse(JSON.stringify(state));
nState.sum += action.data.num;
return nState;
},
},
};
2.4.2 引入model
// src/index.js
import dva from "dva";
import "./index.css";
// 1. Initialize
const app = dva();
// 2. Plugins
// app.use({});
// 3. Model
// app.model(require('./models/example').default);
// 引入Model
app.model(require("./models/count").default);
// 4. Router
app.router(require("./router").default);
// 5. Start
app.start("#root");
2.4.3 使用connect进行同步操作
// src/routes/CountPage.js
import React from "react";
import { connect } from "dva";
function CountPage(props) {
console.log(props);
const { sum, dispatch } = props;
const inc = (num) => {
dispatch({ type: "count/increment", data: { num } });
};
const dec = (num) => {
dispatch({ type: "count/decrement", data: { num } });
};
return (
<div>
<div>{sum}</div>
<button onClick={() => inc(1)}>+1</button>
<button onClick={() => dec(-2)}>-2</button>
</div>
);
}
const mapStateToProps = (state) => {
return {
sum: state.count.sum, // count是命名空间,num是状态
};
};
export default connect(mapStateToProps)(CountPage);
2.5 状态管理 models_effects(异步)
2.5.1 修改model
// src/model/count.js
export default {
// 命名空间
namespace: "count",
// 状态
state: {
sum: 0,
},
// 同步操作
reducers: {
increment(state, action) {
// 这里不能直接修改state返回,需要拷贝一个新的
let nState = JSON.parse(JSON.stringify(state));
nState.sum += action.data.num;
return nState;
},
decrement(state, action) {
// 这里不能直接修改state返回,需要拷贝一个新的
let nState = JSON.parse(JSON.stringify(state));
nState.sum += action.data.num;
return nState;
},
},
// 异步操作
effects: {
// 这里第一个结构赋值的data 要与dispatch一致
*fetchIncrement({ data }, { put, call }) {
// 异步操作,这里假设是1s延迟,会将结果返回给res
// call方法异步操作,第1个是函数,第2个是传参
const res = yield call((_) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(_);
}, 1000);
});
}, data);
// 获得结果后调用同步方法,修改状态
yield put({ type: "increment", data: { num: res.num } });
},
},
};
2.5.2 异步操作
// src/routes/CountPage.js
import React from "react";
import { connect } from "dva";
function CountPage(props) {
console.log(props);
const { sum, dispatch } = props;
const inc = (num) => {
dispatch({ type: "count/increment", data: { num } });
};
const dec = (num) => {
dispatch({ type: "count/decrement", data: { num } });
};
const fetchInc = (num) => {
dispatch({ type: "count/fetchIncrement", data: { num } });
};
return (
<div>
<div>{sum}</div>
<button onClick={() => inc(1)}>+1</button>
<button onClick={() => dec(-2)}>-2</button>
<button onClick={() => fetchInc(2)}>异步+2</button>
</div>
);
}
const mapStateToProps = (state) => {
return {
sum: state.count.sum, // count是命名空间,num是状态
};
};
export default connect(mapStateToProps)(CountPage);
2.6 订阅监听(subscriptions)(以检查登录状态为例)
// src/models/global.js
export default {
// 命名空间
namespace: "global",
// 状态
state: {},
subscriptions: {
setup({ dispatch, history }) {
// 初始化监听
const unlisten = history.listen((location) => {
console.log("路由变化:", location.pathname);
// 示例:检查登录状态
const token = localStorage.getItem("token");
if (!token && location.pathname !== "/login") {
history.push("/login");
}
});
// 返回取消监听函数(可选)
return () => unlisten();
},
},
};
2.7 模拟数据(mock)
2.7.1 创建mock
my-dva/mock.js
注意:不在src下,是在项目根目录下
module.exports = {
"GET /api/mockData": (req, res) => {
console.log(req);
res.send({
name: "喜羊羊",
});
},
};
2.7.2 注册mock
./.roadhogrc.mock.js
注意:不在src下,是在项目根目录下
export default {
...require("./mock/testMock"),
};
src/serivces/example.js
import request from "../utils/request";
// 注册mock接口
export function testMock() {
return request("/api/mockData");
}
2.7.3 获取数据
import React from "react";
import { Link } from "dva/router";
import { testMock } from "../services/example";
export default function UserPage(props) {
testMock().then((res) => {
console.log(res);
});
const handleClick = () => {
props.history.push("/"); //函数式组件可以使用useNavigate
};
return (
<div>
<div>用户页</div>
<Link to="/">跳转到首页</Link>
<button onClick={handleClick}>点击跳转到首页</button>
</div>
);
}