dva是一个基于 redux
和 redux-saga
的轻量级前端框架,它大大简化了数据流管理的复杂度,并提供了一种更高效的方式来组织和管理代码。dva
的名字来源于 Dota 2 的一个英雄——D.Va。
以下是关于 dva
的详细介绍:
简化了 Redux 的使用:dva
提供了一个更简洁的 API,用于定义 reducers
, effects
和 subscriptions
。
基于 Effects 的副作用管理:使用 redux-saga
来处理异步操作和其他副作用。
内置路由:dva
使用 react-router
进行路由管理,使得路由和数据流深度集成。
插件系统:易于扩展,提供了丰富的插件。
轻量:dva
本身非常轻量,大小只有 6kb(minified and gzipped)。
Models:这是 dva
的核心。一个 model 代表一个领域的数据模型,它包括 state
、reducers
、effects
和 subscriptions
。
state
是当前数据模型的状态。reducers
是用于修改状态的纯函数。effects
是处理异步操作的函数(基于 redux-saga
)。subscriptions
是一个源于全局的 event
的订阅方法,例如路由的改变或键盘输入。Router:使用 JSX
配置路由。与 react-router
保持一致,但允许用户使用 JSX 进行配置。
App:这是 dva
实例,是一个具有路由、数据流和服务的应用。
dva
是一个非常强大且易于上手的前端框架,它整合了 redux
, redux-saga
和 react-router
,使得开发者可以更加集中精力于业务逻辑,而不是数据流或路由配置的复杂性。
dva
是一个基于 redux、redux-saga 和 react-router 的轻量级框架,它简化了数据流管理的模型,并整合了多个库的功能,使得开发者可以使用一种更加统一和简洁的方式来构建应用程序。
其中,model
是 dva
的核心概念,表示存储的数据模型。dva
的 model
封装了 Redux 中的 reducers、actions 以及对应的 side effects。
一个典型的 dva
model 定义如下:
{
namespace: 'example', // 当前 Model 的名称。整个应用的 state 中,该 Model 的 state 会被放入 state.example。
state: {}, // 该 Model 当前的状态。数据保存在这里,起到了 `store` 的作用。
reducers: { // Action 处理器,处理同步动作,用来算出最新的 State。
delete(state, action) {
return { ...state, ...action.payload };
},
},
effects: { // Action 处理器,处理异步动作。
*fetch(action, { call, put }) {
const data = yield call(service.fetch, action.payload);
yield put({ type: 'delete', payload: data });
},
},
subscriptions: { // 订阅源,它们的来源仅仅是当前的 global state。
setup({ dispatch, history }) {
history.listen(({ pathname }) => {
if (pathname === '/users') {
dispatch({ type: 'fetch' });
}
});
},
},
}
以下是 model
方法中各属性的详细解释:
yield
语法调用其他 effects、发起 action 请求、调用异步函数等。dispatch
和 history
两个参数,用于监听数据源并根据需要 dispatch 相应的 action。数据源可以是当前的位置信息、Websocket 连接、keyboard 输入、geolocation 变化等。使用 dva
的 model 方法,开发者可以更高效地组织和管理应用的状态和逻辑,而不需要深入理解 Redux 和 redux-saga 的复杂性。
// 导入React库
import React from 'react';
// 导入dva库和connect方法
import dva, { connect } from 'dva';
// 创建一个dva应用实例
const app = dva();
// 为应用添加一个model
app.model({
// model的命名空间
namespace: 'counter',
// model的初始状态
state: { number: 0 },
// 定义model中的reducers来处理state变化
reducers: {
// 定义一个add的reducer来增加计数值
add(state) {
// 返回更新后的状态值
return { number: state.number + 1 };
}
}
});
// 定义Counter组件
function Counter(props) {
// 返回组件的渲染内容
return (
<div>
// 显示计数器的值
<p>{props.number}</p>
// 添加一个按钮,点击时派发一个“counter/add”的action
<button onClick={() => props.dispatch({ type: "counter/add" })}>+</button>
</div>
)
}
// 使用connect方法连接Counter组件和Redux store
const ConnectedCounter = connect(
// 将state映射到Counter组件的props
(state) => state.counter
)(Counter);
// 定义应用的路由配置
app.router(() => <ConnectedCounter />);
// 启动dva应用并挂载到id为root的DOM元素上
app.start('#root');
src\index.js
import React from 'react';
import dva, { connect } from 'dva';
+function delay(ms) {
+ return new Promise((resolve) => {
+ setTimeout(function () {
+ resolve();
+ }, ms);
+ });
+}
const app = dva();
app.model({
namespace: 'counter',
state: { number: 0 },
reducers: {
add(state) {
return { number: state.number + 1 };
}
},
+ effects: {
+ *asyncAdd(action, { call, put }) {
+ yield call(delay, 1000);
+ yield put({ type: 'add' });
+ }
+ }
});
function Counter(props) {
return (
<div>
<p>{props.number}</p>
<button onClick={() => props.dispatch({ type: "counter/add" })}>+</button>
+ <button onClick={() => props.dispatch({ type: "counter/asyncAdd" })}>异步+</button>
</div>
)
}
const ConnectedCounter = connect(
(state) => state.counter
)(Counter
);
app.router(() => <ConnectedCounter />);
app.start('#root');
dva
是一个基于 redux 和 redux-saga 的数据流方案,同时它还内置了 react-router 和 fetch,所以你可以认为它是一个针对 react 的轻量级应用框架。
在 dva
中,router
是用来定义路由的。路由是UI与URL之间的映射关系,用于定义一个应用的导航结构。
src\index.js
import React from 'react';
import dva, { connect } from 'dva';
import { HashRouter, Switch, Route } from 'dva/router';
function delay(ms) {
return new Promise((resolve) => {
setTimeout(function () {
resolve();
}, ms);
});
}
const app = dva();
app.model({
namespace: 'counter',
state: { number: 0 },
reducers: {
add(state) {
return { number: state.number + 1 };
}
},
effects: {
*asyncAdd(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'add' });
}
}
});
function Counter(props) {
return (
<div>
<p>{props.number}</p>
<button onClick={() => props.dispatch({ type: "counter/add" })}>+</button>
<button onClick={() => props.dispatch({ type: "counter/asyncAdd" })}>异步+</button>
</div>
)
}
const ConnectedCounter = connect(
(state) => state.counter
)(Counter
);
const Home = () => <div>Home</div>
app.router(() => (
<HashRouter>
<>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/counter">Counter</Link></li>
</ul>
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/counter" component={ConnectedCounter} />
</Switch>
</>
</HashRouter>
));
app.start('#root');
在dva
框架中,routerRedux
是一个帮助你进行路由跳转和查询操作的工具。它实际上是对react-router-redux
库的一个简化封装,提供了在Redux Saga或Redux中进行路由操作的能力。
其中,routerRedux.push
是该工具的一个方法,允许你进行路由跳转。它的工作方式与react-router
的history.push
相似。
使用方法:
当你希望在某个异步操作后进行路由跳转时,可以在effects
中使用routerRedux.push
。
import { routerRedux } from 'dva/router';
effects: {
*submitLogin({ payload }, { call, put }) {
const response = yield call(loginService, payload);
if (response.status === 'ok') {
yield put(routerRedux.push('/dashboard'));
} else {
// 处理错误
}
},
},
在上述代码中,假设登录服务成功返回,用户将被重定向到仪表板页面。
在组件中,你通常不直接使用routerRedux.push
,但可以通过dispatch
来触发一个action,然后在对应的model的effects中进行路由跳转。
function MyComponent({ dispatch }) {
const goToDashboard = () => {
dispatch(routerRedux.push('/dashboard'));
}
return (
<button onClick={goToDashboard}>Go to Dashboard</button>
);
}
参数:
routerRedux.push
接受与history.push
相同的参数,这意味着你可以传递一个路径字符串或一个location对象。
例如:
dispatch(routerRedux.push('/list'))
跳转到列表页面dispatch(routerRedux.push({ pathname: '/list', search: '?sort=name' }))
跳转到列表页面并附加查询参数总结:
routerRedux.push
在dva
框架中提供了一种在Redux中进行路由操作的方法,这使得路由跳转可以像其他副作用操作一样在effects中处理,保持了代码的一致性和清晰度。
src\index.js
import React from 'react';
import dva, { connect } from 'dva';
import { HashRouter, Link, Switch, Route, routerRedux } from 'dva/router';
function delay(ms) {
return new Promise((resolve) => {
setTimeout(function () {
resolve();
}, ms);
});
}
const app = dva();
app.model({
namespace: 'counter',
state: { number: 0 },
reducers: {
add(state) {
return { number: state.number + 1 };
}
},
effects: {
*asyncAdd(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'add' });
},
*goto({ to }, { put }) {
yield put(routerRedux.push(to));
}
}
});
function Counter(props) {
return (
<div>
<p>{props.number}</p>
<button onClick={() => props.dispatch({ type: "counter/add" })}>+</button>
<button onClick={() => props.dispatch({ type: "counter/asyncAdd" })}>异步+</button>
<button onClick={() => props.dispatch({ type: "counter/goto", to: '/' })}>跳转到/</button>
</div>
)
}
const ConnectedCounter = connect(
(state) => state.counter
)(Counter
);
const Home = () => <div>Home</div>
app.router(() => (
<HashRouter>
<>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/counter">Counter</Link></li>
</ul>
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/counter" component={ConnectedCounter} />
</Switch>
</>
</HashRouter>
));
app.start('#root');
subscriptions
是 dva
框架中的一个关键概念。它允许你订阅一个数据源,然后根据条件 dispatch 相应的 action。通常在应用启动时使用,例如动态地从服务器加载数据、添加键盘快捷键、监听路由变化等。
在 dva
中,subscriptions
是一个对象,其中每一个键都是一个订阅函数。每一个订阅函数接收一个 dispatch
和一个 history
(用于路由操作)作为参数。
npm install keymaster --save
import React from 'react';
import dva, { connect } from 'dva';
import { HashRouter, Link, Switch, Route, routerRedux } from 'dva/router';
import key from 'keymaster';
function delay(ms) {
return new Promise((resolve) => {
setTimeout(function () {
resolve();
}, ms);
});
}
const app = dva();
app.model({
namespace: 'counter',
state: { number: 0 },
reducers: {
add(state) {
return { number: state.number + 1 };
}
},
effects: {
*asyncAdd(action, { call, put }) {
yield call(delay, 1000);
yield put({ type: 'add' });
},
*goto({ to }, { put }) {
yield put(routerRedux.push(to));
}
},
subscriptions: {
keyboardWatcher({ dispatch }) {
key('a', () => {
dispatch({ type: 'counter/add' });
});
},
}
});
function Counter(props) {
return (
<div>
<p>{props.number}</p>
<button onClick={() => props.dispatch({ type: "counter/add" })}>+</button>
<button onClick={() => props.dispatch({ type: "counter/asyncAdd" })}>异步+</button>
<button onClick={() => props.dispatch({ type: "counter/goto", to: '/' })}>跳转到/</button>
</div>
)
}
const ConnectedCounter = connect(
(state) => state.counter
)(Counter
);
const Home = () => <div>Home</div>
app.router(() => (
<HashRouter>
<>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/counter">Counter</Link></li>
</ul>
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/counter" component={ConnectedCounter} />
</Switch>
</>
</HashRouter>
));
app.start('#root');