什么是DvaJS
DvaJS是一个基于redux和redux-saga的数据流方案,然后为了简化开发体验,dva 还额外内置了react-router和fetch,是一个轻量级的前端应用框架。
何时使用
一般不是过于复杂的组件通讯,都不建议使用dva,一般来说使用函数式组件,通过父子组件传值即可,当业务层面删除线组件间通信多,业务复杂,需要引入状态管理再使用Dva会更好,避免长此以往项目的技术层面的臃肿与冗余。
怎样使用
使用dva version 2.4.1新建一个项目的结构如图1,assets为存放静态资源的地方,components为定义公用组件的文件,models为数据源定义的文件,routes为页面存放的文件,services为定义接口通讯的文件,utils为定义公用方法的文件。
index为入口文件,内容如图代码块1,是dva实例化和引入插件,引入models,路由,和挂载节点的地方,其中要注意的点有以下几点
1.Dva的实例是可以有多个的。
2.实例化Dva时可以传入一个options用于控制实例Dva的属性,例如该实例的路由方式,通过对象中的history属性传入不同的值即可控制,又或是初始的数据,通过initialState给入到对应的model中,注意通过initialState传入初始值会覆盖model原本的state值,等一系列初始化的操作都是在这里完成的。
3.使用插件,引入对应的插件然后use即可。
4.引入需要的数据源。
5.引入对应的路由。
6.挂载到对应的节点
图一
import dva from 'dva';
import createLoading from 'dva-loading';
import {createBrowserHistory as createHistory} from 'history';
import './index.css';
// 1. Initialize
//dva(options)
const options = {
history, // 指定给路由用的 history,默认是 hashHistory
initialState:{
history:createHistory(),
//key值为model文件的namespace
example:{
initFlag:1
}
}, // 指定初始数据,优先级高于 model 中的 state
onError, // effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态。
...
}
const app = dva(options);
// 2. Plugins
app.use(createLoading());
// 3. Model
app.model(require('./models/example').default);
// 4. Router
app.router(require('./router').default);
// 5. Start
app.start('#root');
代码块1
初始化完了Dva后,我们进行一个业务功能的开发。过程为1.定义路由,2.编写UI组件,3.定义Model数据源,4.将UI组件和数据源连接起来,这样一个使用Dva编写一个业务组件的过程就完成了,让我们来看看每一步需要注意到的细节。
1.定义路由,如代码块2
import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import IndexPage from './routes/IndexPage';
function RouterConfig({ history }) {
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={IndexPage} />
</Switch>
</Router>
);
}
代码块2
2.编写 UI Component,如代码块3,其中mapStateToProps将model中的state数据源传入UI组件中,mapDispatchToProps是定义各种操作model中的state的方法然后作为props传入UI组件中。
import React, { useEffect } from 'react';
import { connect } from 'dva';
import styles from './IndexPage.css';
function IndexPage(props) {
return (
<span>这是一个Dva示例</span>
);
}
export default connect(mapStateToProps,mapDispatchToProps)(IndexPage);
//此方法是将model中的state数据源传入UI组件中
function mapStateToProps (state){
return {
...state
}
}
function mapDispatchToProps(dispatch){
return {
updateState:(payload)=>{
return dispatch({
type:'example/updateState',
payload
})
}
}
}
代码块3
3.定义 Model
export default {
namespace: 'example',
state: {
personList:[]
},
effects:{
*fetchIndexListData(payload,{put,call})=>{
const res = yield call(xxxserver,payload)
yield put({type:'updateState',payload:res})
}
}
reducers: {
updateState(state, action) {
return { ...state, ...action.payload };
},
},
subscriptions:{
setup({history,dispatch}){
return history.listen({pathname}) =>{
if(pathname==='/'){
dispatch({type:'fetchIndexListData'})
}
}
}
}
};
4.connect
将UI Component和model连接起来
其中mapStateToProps的作用是将model中的state作为props传给UI Component,mapDispatchToProps的作用是提供一个可以定义操纵model中的state的方法的地方
export default connect(mapStateToProps,mapDispatchToProps)(IndexPage);
function mapStateToProps (state){
return {
state
}
}
function mapDispatchToProps(dispatch){
return {
add:(payload)=>{
return dispatch({
type:'example/save',
payload
})
}
}
}
Dva 核心概念
数据流方案
dva数据流方案是基于 Redux 理念的数据流向,数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明。
- 基于 Redux 的基本概念。包括:
- State 数据,通常为一个 JavaScript 对象,操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。
- Action 行为,一个普通 JavaScript 对象,它是改变 State 的唯一途径。
- dispatch,一个用于触发 action 改变 State 的函数。
- Reducer 描述如何改变数据的纯函数,接受两个参数:已有结果和 action 传入的数据,通过运算得到新的 state。
- Effects(Side Effects) 副作用,常见的表现为异步操作。dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制。
- Connect 一个函数,绑定 State 到 View
Dva数据流的过程
- 代理 router 和 start 方法,实例化 app 对象
- 调用 dva-core 的 start 方法,同时渲染视图
- 使用 react-redux 完成了 react 到 redux 的连接。
Dva 与 React、React-Redux、Redux-Saga 之间的差异
redux
- 状态及页面逻辑从 里面抽取出来, 成为独立的 store, 页面逻辑就是 reducer
- 及都是 Pure Component, 通过 connect 方法可以很方便地给它俩加一层 wrapper 从而建立起与 store 的联系: 可以通过 dispatch 向 store 注入 action, 促使 store 的状态进行变化, 同时又订阅了 store 的状态变化, 一旦状态变化, 被 connect 的组件也随之刷新
- 使用 dispatch 往 store 发送 action 的这个过程是可以被拦截的, 自然而然地就可以在这里增加各种 Middleware, 实现各种自定义功能, eg: logging
这样一来, 各个部分各司其职, 耦合度更低, 复用度更高, 扩展性更好。
saga
因为我们可以使用 Middleware 拦截 action, 这样一来异步的网络操作也就很方便了, 做成一个 Middleware 就行了, 这里使用 redux-saga 这个类库, 举个栗子:
- 点击创建 Todo 的按钮, 发起一个 type == addTodo 的 action
- saga 拦截这个 action, 发起 http 请求, 如果请求成功, 则继续向 reducer 发一个 type == addTodoSucc 的 action, 提示创建成功, 反之则发送 type == addTodoFail 的 action 即可
Dva
- 把 store 及 saga 统一为一个 model 的概念, 写在一个 js 文件里面
- 增加了一个 Subscriptions, 用于收集其他来源的 action, 比如键盘操作等
- model 写法很简约, 类似于 DSL(领域特定语言),可以提升编程的沉浸感,进而提升效率