dva介绍
Dva首先是一个基于redux和redux-saga的数据流方案,然后为了简化开发体验,Dva还额外内置了react-router和fetch,所以也可以理解为一个轻量级的应用框架。
Dva设计的目的就是简化元素,降低难度,让你不用管他怎么实现的,我们按照默认的这个规则去写就可以,大致就相当于 它写好了规则必须按照规则来。
Dva是一个约定式的model组织方式
符合以下规则的文件会被认为是 model 文件,
src/models下的文件src/pages下,子目录中 models 目录下的文件src/pages下,所有 model.ts 文件(不区分任何字母大小写)
dva是umi内置的一款插件,主要用来管理数据流
| redux | dva | |
|---|---|---|
| 状态数据 | state | state |
| 行为描述 | action | action |
| 无副作用业务 | reducer | reducer |
| 有副作用业务 | creators | effect |
| 通讯请求修改状态函数 | dispatch | dispatch |
| 通讯请求获取状态函数 | connext | connect |
| 从源获取 | 无 | subscription |
数据流向
数据的改变一般是由用户或者浏览器的一些交互行为,当这些行为会去改变数据时候, 组件通过dispatch的请求,这个请求是一个对象(action),如果这个action是同步的会通过reducer直接改变state,如果是异步的或者带有副作用的effect然后流向reducer最终改变state,state会通过connect函数流回组件
分层开发
主要的分层结构:
- Page 负责与用户直接打交道:渲染页面,接受用户的操作输入,侧重于展示型交互性逻辑,这里需要了解无状态组件
- Model 负责处理业务逻辑,为 Page 做数据、状态的读写、变换、暂存等,Dva中model就是做了这一层的操作
- Service 负责与 HTTP 接口对接,进行纯粹的数据读写
基础概念
- namespace
- model的命名空间,同时也是他在全局
state上的属性 - 只能用字符串,不支持通过
.的方式创建多层命名空间,相当于这个model的key - 在组件里面,通过
connect这个key将想要引入的model加入
- model的命名空间,同时也是他在全局
import { connect } from 'dva';
export default connect(({ namespaceValue }) => ({ ...namespaceValue }))(DvaCompoent);
-
state
- 表示
model的状态数据 - 操作的时候每次都要当作不可变数据
immutable data来对待,保证每次都是全新对象,没有引用关系
- 表示
-
reducer
- 必须是纯函数,有固定输入输出,主要目的是修改自身state
- 接受两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果,该函数把一个集合归并成一个单值
- 需要注意的是同样的输入必然得到同样的输出,它们不应该产生任何副作用effect。并且,每一次的计算都应该使用immutable data
-
effect
- 主要用于异步请求,接口调用之类的
effect被称为副作用,在我们的应用中,最常见的就是异步操作- 它来自于函数编程的概念,之所以叫副作用是因为它使得我们的函数变得不纯,同样的输入不一定获得同样的输出
-
subscription
- subscription语义是订阅,用于订阅一个数据源,然后根据条件dispatch需要的action
- 数据源可以是当前的时间、服务器的websocket连接、keyboard输入、geolocation变化、history路由变化等等
- 内部定义的函数都会被被执行,执行之后作为监听来处理事务
-
dispatch
- dispatch是一个用于触发action的函数,action是改变state的唯一途径,但是它只描述了一个行为,而dipatch可以看作是触发这个行为的方式,reducer则是描述如何改变数据的
- 在Dva中,connect model的组件通过props可以访问到dispatch,可以调用model中的reducer或者effects
import { connect } from 'dva';
const testCom = props => {
const { dispatch } = props;
const changeValue = (id, val) => {
// 调用reducer,一般是同步修改state中的值
dispatch({
type: 'dva/save',
payload: {
param: val
},
});
// 调用effect,一般是发送后台请求
dispatch({
type: 'dva/queryValue',
payload: {
id: id
},
});
};
return(
<div>'hello world'</div>
)
}
export default connect(({ dva }) => ({ ...dva }))(testCom);
1.Reducer
putOrderIncomeFee(state, action) {
return { ...state, ...action.payload };
},
用来处理同步操作。如果不需要调接口时候,我们前台传递的 action可以直接调用 reducers里的方法。
reducer是一个函数,接收state 和 action,返回老的或新的state。
state:为当前 Model 下的所有 state 值,可以 console.log(state) 看一下输出就知道了。 action:当前台页面需要进行数据操作时,就会创建一个 action,action 存放了传递过来需要对当前 state 进行改变的数据。
2.Effect
用来处理异步操作。如果需要调取接口的话,前台页面就需要调用 effects 里的方法。内部使用 yield 关键字,标识每一步的操作
每一个 effect 都可以接收两个参数:
- 包含 dispatch 携带参数 payload 的 action 对象
- dva 提供的 effect 函数内部的处理函数集
第二个参数提供的处理函数中,常用的有 call、put、select
- call: 执行异步函数(与后台服务端接口进行交互)
- 第一个传参:后台服务器接口对应的名称。
- 第二个参数:入参。
- 请求成功之后,调用 reducer 更新 state
- put: 发出一个 Action,类似于dispatch 触发reducer改变state
- 用来发出事件,即 action。一般调用 reducers 下的方法进行同步数据。
- type:该 Model 层里 reducers 下的方法名。
- payload:参数的传递。
- select: 返回 model 中的 state //用于从state里获取数据
- const m = yield select((state) => state.test.num)
3.subscriptions
订阅监听 比如:比如我们监听路由,进入页面就如何,可以在这写
setup ({ dispatch, history, query }) {
return history.listen(
async ({ pathname, search, query}) => {
if (pathname==="/testdemo")
{
// 当进入testdemo这路由,就会触发fetchUser方法
dispatch({ type: "fetchUser" })
}
})
}
注意
-
yield call:执行异步请求,阻塞流程
-
yield put:执行effects中的异步方法时,不阻塞流程,和组件中使用dispatch执行effects方法一样;但在执行reducer中的状态更新方法时,会阻塞流程