我们来介绍一下,dva出自于暴雪出品的一款游戏《守望先锋》,援引官方的角色介绍:
D.Va拥有一部强大的机甲,它具有两台全自动的近距离聚变机炮、可以使机甲飞跃敌人或障碍物的推进器、还有可以抵御来自正面的远程攻击的防御矩阵。
然后呢,蚂蚁金服的一位架构师sorrycc很迷这位美女,正巧刚开发了一款前端框架没有名字,作为一个向女神献礼的项目,dva框架就此诞生。
我们先看看React 没有解决的问题
React 没有解决的问题 |
React 本身只是一个 DOM 的抽象层,使用组件构建虚拟 DOM。
如果开发大应用,还需要解决一个问题。
- 通信:组件之间如何通信?
- 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等
通信问题 |
组件会发生三种通信。
- 向子组件发消息
- 向父组件发消息
- 向其他组件发消息
React 只提供了一种通信手段:传参。对于大应用,很不方便。
数据流问题 |
目前流行的数据流方案有:
- Flux,单向数据流方案,以 Redux 为代表
- Reactive,响应式数据流方案,以 Mobx 为代表
- 其他,比如 rxjs 等
到底哪一种架构最合适 React ?
目前最流行的数据流方案:
- 路由: React-Router
- 架构: Redux
- 异步操作: Redux-saga
缺点:要引入多个库,项目结构复杂。
何为 dva |
dva 是体验技术部开发的 React 应用框架,将上面三个 React 工具库包装在一起,简化了 API,让开发 React 应用更加方便和快捷。
dva = React-Router + Redux + Redux-saga
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了react-router 和 fetch。
实际上,dva只是基于现有开源框架的一层轻量封装,并没有引入任何新概念:
- React:管理View
- react-router:管理路由
- Redux:管理Model
- redux-saga:管理异步调用(副作用)
dva 应用的最简结构 |
import dva from 'dva';
const App = () => <div>Hello dva</div>;
// 创建应用
const app = dva();
// 注册视图
app.router(() => <App />);
// 启动应用
app.start('#root');
dva 应用的最简结构(带 model) |
// 创建应用
const app = dva();
// 注册 Model
app.model({
namespace: 'count',
state: 0,
reducers: {
add(state) {
return state + 1 },
},
effects: {
*addAfter1Second(action, {
call, put }) {
yield call(delay, 1000);
yield put({
type: 'add' });
},
},
});
// 注册视图
app.router(() => <ConnectedApp />);
// 启动应用
app.start('#root');
Dva 概念 |
数据流图 |
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 通过server交互获取数据然后流向 Reducers 最终改变 State,同样state通过connect将model、状态数据与组件相连
总的数据流图:
数据流图1:最简单 |
- State:一个对象,保存整个应用状态
- View:React 组件构成的视图层
- Action:一个对象,描述事件
- connect 方法:一个函数,绑定 State 到 View
- dispatch 方法:一个函数,发送 Action 到 State
数据流图2:使用中间件 |
数据流图3:使用中间件 副作用 |
View |
View 就是 React 组件构成的 UI 层,从 State 取数据后,渲染成 HTML 代码。只要 State 有变化,View 就会自动更新。
Models |
- namespace: 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成
- state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
- reducers: Action 处理器,处理同步动作,用来算出最新的 State
- effects:Action 处理器,处理异步动作
namespace |
model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 .
的方式创建多层命名空间。
当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成。
在组件里面,通过connect将这个key引入到相应的model中。
State |
初始值,优先级低于传给 dva() 的 opts.initialState。
State 是储存数据的地方,收到 Action 以后,会更新数据。
State 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值);
const app = dva({
initialState: {
count: 1 },
});
app.model({
namespace: 'count',
state: 0,
});
此时,在 app.start()
后 state.count 为 1 。
Reducer |
Action 处理器,处理同步动作,用来算出最新的 State
type Reducer<S, A> = (state: S, action: A) => S
以 key/value 格式定义 reducer。用于处理同步操作,唯一可以修改 state 的地方。由 action 触发。
格式为 (state, action) => newState 或 [(state, action) => newState, enhancer]
reducers: {
save(state, action) {
return {
...state, ...action.payload };
},
},
📌Action
- Action 是用来描述 UI 层事件的一个 javascript 对象
- 它是改变 State 的唯一途径。
- 通过 dispatch 函数调用一个 action
- 必须包含type字段
{
type: 'click-submit-button',
payload: this.form.data
}
Effect |
Action 处理器,处理异步动作,不直接修改 state。Effect 指的是副作用。根据函数式编程,计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写。副作用是因为它使函数变得不纯,同样的输入不一定获得同样的输出。
effects: {
*fetch({
payload }, {
call, put }) {
// eslint-disable-line
yield put({
type: 'sav