dva使用笔记
1. dva
- 基于
redux
、redux-saga
和react-router
的轻量级前端框架。 - dva是基于react+redux最佳实践上实现的封装方案,简化了redux和redux-saga使用上的诸多繁琐操作
- 集成封装了react、react-dom、react-router-dom、connected-react-router、redux、redux-saga
2、dva机制
dva是通过用户交互行为或者浏览器URL跳转行为来改变数据的。当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State
如图:
3、概念
3.1 state 状态
state和redux的状态一样,表示Model的状态数据,操作的时候不可改变元数据,保证每次都是全新对象。
3.2 action 动作
- 改变state的唯一途径
- 任何行为想改变state都需要dispatch一个action
- action必须包含一个
type
属性// dispatch(Action); dispatch({ type: 'add', payload: 'Learn Dva' });
3.3 dispatch 派发
- dispatch是触发action的函数
- action只是一个行为,dispatch是触发这个行为的方式。
- 在dva中,connect Model的组件通过props可以访问到dispatch,可以调用Model中的reducer或者effect。
dispatch({ type: 'add', // 定义类型,进行判断然后操作 other: {}, // 需要传递的其他的信息 });
3.4 reducer 处理器
reducer是一个函数,接收state 和 action,返回老的或新的state。
reducers: {
add(state, { payload: todo }) {
const todos = state.todos.concat(todo);
return { ...state, todos };
},
}
3.5 Effect 副作用
- Effect通常用来处理异步操作。
- 之所以叫副作用是因为它使得我们的函数变得不纯,同样的输入不一定获得同样的输出。
- dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。
3.6 Subscription 订阅
Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action, 数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
3.7 Router 路由
就是前端路由
3.8 Route Components 路由组件
一般一个页面作为一个路由组件。
4、使用
$ create-react-app dva-app
yarn add dva
把src目录下的内容删了。
// src/index.js
import React from 'react';
import dva, { connect } from 'dva';
import { Router, Route } from 'dva/router';
// 监听键盘 需要安装 yarn add keymaster
import keymaster from 'keymaster';
let app = dva();
// 定义模型
app.model({
// 每个模型都有自己的命名空间
namespace: 'name1',
// 每个模型都有自己的状态
state: {
num: 1
},
// 处理器
reducers: {
// 处理器的属性名就是action-type,值是一个函数,用来计算新状态
// 例如:store.dispatch({type: 'name1/add'}), 这个时候就会派发给add()函数来执行
add(state, action){
return {num: statenum + 1};
},
// store.dispatch({type: 'name1/minus'})
minus(state, action){
return {num: statenum - 1};
}
},
// 异步操作
// 如上面的图所示,当派发阶段,会同时派发给reducer和effect,然后根据属性名来匹配要执行的方法
effects: {
*asyncAdd(action, effect){
// put: 派发动作, call:调用方法 select是个函数 yield select( state => state.num );
let { put, call, select } = effect;
// fn是一个promise函数,需要自己实现,一般为调接口
yield call(fn, 1000);
// 给当前模型派发action不能加命名空间,给其他模型派发需要加命名空间
yield put({type: 'add'});
}
},
subscriptions: {
change({history}){
history.listen((location)=>{
console.log(location);
})
},
keyboard({dispatch}) {
// keymaster('要监听的按键', 回调函数)
keymaster('space', () => {
dispatch({type: 'add'});
})
}
}
})
app.model({
namespace: 'name2',
state: {
num: 1
},
reducers: {
// store.dispatch({type: 'name2/add'})
add(state, action){
return {num: statenum + 1};
},
minus(state, action){
return {num: statenum - 1};
}
}
})
function Counter1(props){
return {
<div>
<p>{props.num}</p>
<button onClick={ () => props.dispatch({type: 'name1/add'})}> + </button>
<button onClick={ () => props.dispatch({type: 'name1/asyncAdd'})}> + </button>
<button onClick={ () => props.dispatch({type: 'name1/minus'})}> - </button>
</div>
}
}
function ConnectedCounter1 = connect(state => state.counter1)(Counter1);
function ConnectedCounter2 = connect(state => state.counter2)(Counter2);
app.router(({ history })=> (
<Router history={history}>
<>
<Route path='/counter1' component={ConnectedCounter1}/>
<Route path='/counter2' component={ConnectedCounter2}/>
</>
</Router>
) );
app.start('#root');