前言

我开通了一个微信公共号“王和阳的航海日志”,在上面记录着自己的学习、思考、实践和成长的过程,欢迎关注、交流和拍砖。




最近在项目里用了DVA,一个基于Redux的前端应用开发框架,用dva能让我们省去配置项目的一堆麻烦事儿。关于Dva的使用和介绍这里就不多说了,官方文档已经讲得很详细了。下面简单结合我自己的实践经历,讲讲DVA的一些思想以及一些关于数据流向的想法。

Dva的框架和由来

一图胜前言,首先我们看下传统的React项目的组件结构是怎么样的:



如果<TodoList/>和<AddTodoBtn/>想要发生联系,则只能通过父组件<App/>
来实现,这个方式在页面比较简单的时候还能够胜任,但若项目复杂起来之后,整个页面的数据流向就会变得如下图所示:



在这种情况下对项目进行维护绝对是个灾难,同时对这样的项目进行改动也是很困难的事儿,因为不同组件之间耦合的地方太多了,所以我们需要对state
做额外的管理,于是我们就使用了Redux,那么项目结构就变成了这样:



可以看到这已经把state和处理逻辑从 里面抽取出来了, 原先的add和finish操作也变成了reducer里的函数。因为<TodoList/> 及
<AddTodoBtn/>都是 Pure Component
<https://reactjs.org/docs/react-api.html#reactpurecomponent>, 那么通过 connect
方法使这些组件建立起与store 的联系,同时通过 dispatch 向 store 发送 action, 促发 store 的状态进行变化, 一旦状态有变,
因为组件和store是被 connect,那么组件也就会随之更新,而且这个过程是可以被拦截的,所以我们就可以很方便地增加各种 Middleware
,从而实现各种自定义功能, 例如log、高阶组件,并且这样的结构耦合度更低, 复用度更高, 扩展性更好。

需要注意的是,上面的项目结构其实是不包含异步处理的,则为了处理异步请求,我们引入了redux-sage
<https://redux-saga-in-chinese.js.org/>,redux-saga
会拦截异步action并发起http请求,以发送type=add的异步action网络请求来说,若请求成功,则继续发送一个type=addSuccess的
action,若请求失败,则发送一个type=addFail的action。

在了解了上面这些概念后,使用了dva后项目的结构也就呼之欲出了:

dva本身就是React + Redux + saga的组合,而图中红框的部分就是dva,可以很清楚地看到,dva把store和saga
结合成了一个model,同时增加了一个subscription来订阅其他来源的action(例如键盘操作、路由变化)

下面简单总结下dva中的一些概念以及自己的一些思考

state

state 表示 Model 的状态数据,通常表现为一个 javascript
对象(当然它可以是任何值),且需要把这个state当做不可变数据(immutable
data)来处理,保证每次得到的state都是全新对象,新旧对象间不存在引用关系,在检测两个state
里的复杂对象时只需要比较其属性的引用即可,这样做的好处是能够提升性能,便于测试和追踪变化(可用来实现神奇又好玩的时光机穿梭神器redux-devtool
<https://github.com/reduxjs/redux-devtools)>)。具体到代码里就如下图所示:
state: { keyword: '', moduleList: [], }, ..... return { ...state, keyword };
action 和 dispatch

action 是改变 state 的唯一途径,是一个普通的 javascript 对象,它描述了一个行为且是改变 state
的唯一途径。从用户UI操作事件、网络请求回调和 WebSocket 等其他地方获得的数据,最终都会通过dispatch 函数调用一个 action
,从而改变对应的数据。action 必须带有 type 指明具体的行为名称,且能附带上额外的信息。

dispatching 是一个用于触发 action 的函数,且 dispatch 是在组件 connect Models以后,才能通过 props 传入。
action和dispath存在的意义就在于将“如何改变数据”和“改变数据”这两件事分开进行,让我们对如何改变state有更清晰的思路。
dispatch({ type: 'add', payload:{ item:'a' } });
reducer

reducer 是描述如何改变数据的,它接受两个参数(之前已经累积运算的结果和当前要被累积的值),并返回一个新的累积结果。

通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。需要注意的是 Reducer 必须是纯函数
,即同样的输入必然得到同样的输出,它们不应该产生任何副作用,或者用更加专业的话说,这个函数是幂等的。

effect

effect
被称为副作用,在我们的应用中,最常见的就是异步操作(例如网络请求)。它来自于函数式编程的概念,之所以叫副作用是因为同样的输入不一定获得同样的输出。现在函数式编程大行其道的原因主要有以下两点:

* 函数式编程是面向数学的抽象,更加符合人的思维逻辑
* 函数是引用透明且没有副作用,不依赖外部的状态也不改变外部的状态。
* 因为函数是不可变的,则由于多个线程之间不共享状态,不会造成资源争用(Race condition),也就不需要用锁
来保护可变状态,也就不会出现死锁,这样可以更好地进行并发,尤其是在对称多处理器(SMP)架构下能够更好地利用多个核提供的并行处理能力。要知道现在计算机的计算能力的增加已经不是依靠主频频率的提升,而是更依赖于CPU核心个数的增加,这一点带来的好处是母庸置疑的
5 <https://www.zhihu.com/question/28292740/answer/40336090>。
(如果你想了解更多关于函数式编程的信息,可以阅读JS函数式编程指南
<https://www.gitbook.com/book/llh911001/mostly-adequate-guide-chinese/details>。)

dva 为了控制副作用的操作,引入了redux-sagas
<http://superraytin.github.io/redux-saga-in-chinese>做异步流程控制,且因为采用了generator的相关概念
<http://www.ruanyifeng.com/blog/2015/04/generator.html>
,所以能将异步转成同步写法,从而将effects转为纯函数。
effects: { * createModule({ payload: { formData} }, { call, put }) { const
result =yield call(Services.createModule, formData); if (result) { yield put({
type:'startSearch'}); } } }
subscription

subscriptions 是一种从 源 获取数据的方法,它来自于 elm。

Subscription 用于订阅一个数据源,然后根据条件 dispatch需要的action。数据源可以是服务器的 websocket
连接、keyboard 输入、geolocation 变化、history 路由变化等等。
app.model({ namespace: 'count', subscriptions: { setup({ dispatch, history })
{return history.listen(({ pathname }) => { // 第1次进入页面,请求模块数据 if (pathname ===
'/module') { dispatch({ type: 'startSearch'}); } }); }, } });
简短的完整代码
import dva, { connect } from 'dva'; import { Router, Route } from 'dva/router'
; import React from'react'; import styles from './index.less'; import key from
'keymaster'; const app = dva(); app.model({ namespace: 'count', state: { record:
0, current: 0, }, reducers: { add(state) { const newCurrent = state.current + 1;
return { ...state, record: newCurrent > state.record ? newCurrent :
state.record, current: newCurrent, }; }, minus(state) {return { ...state,
current: state.current -1}; }, }, effects: { *add(action, { call, put }) { yield
call(delay,1000); yield put({ type: 'minus' }); }, }, subscriptions: {
keyboardWatcher({ dispatch }) { key('⌘+up, ctrl+up', () => { dispatch({type:
'add'}) }); }, }, }); const CountApp = ({count, dispatch}) => { return ( <div
className={styles.normal}> <div className={styles.record}>Highest Record:
{count.record}</div> <div className={styles.current}>{count.current}</div> <div
className={styles.button}> <button onClick={() => { dispatch({type:
'count/add'}); }}>+</button> </div> </div> ); }; function
mapStateToProps(state) { return { count: state.count }; } const HomePage =
connect(mapStateToProps)(CountApp); app.router(({history}) =><Router history=
{history}> <Route path="/" component={HomePage} /> </Router> );
app.start('#root'); // --------- // Helpers function delay(timeout){ return new
Promise(resolve => { setTimeout(resolve, timeout); }); }
引用

* dva图解 <https://yuque.com/flying.ni/the-tower/tvzasn>
* dva介绍 <https://github.com/dvajs/dva/blob/master/README_zh-CN.md>
* redux调试神器之redux-devtools <https://github.com/reduxjs/redux-devtools>
* immutable data
<https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md#reasonable>
* redux-sagas <http://superraytin.github.io/redux-saga-in-chinese>
* 什么是函数式编程思维? <https://www.zhihu.com/question/28292740/answer/40336090>

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信