<span>单向数据流-从共享状态管理:flux/redux/vuex漫谈异步数据处理</span>
不管是Vue,还是 React,都需要管理状态(state),比如组件之间都有共享状态的需要。
什么是共享状态?
比如一个组件需要使用另一个组件的状态,或者一个组件需要改变另一个组件的状态,都是共享状态。
父子组件之间,兄弟组件之间共享状态,往往需要写很多没有必要的代码,比如把状态提升到父组件里,或者给兄弟组件写一个父组件,听听就觉得挺啰嗦。
如果不对状态进行有效的管理,状态在什么时候,由于什么原因,如何变化就会不受控制,就很难跟踪和测试了。如果没有经历过这方面的困扰,可以简单理解为会搞得很乱就对了。
对于状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。
Store模式
最简单的处理就是把状态存到一个外部变量里面,比如:this.$root.$data,当然也可以是一个全局变量。但是这样有一个问题,就是数据改变后,不会留下变更过的记录,这样不利于调试。
所以我们稍微搞得复杂一点,用一个简单的 Store 模式
var store = { // 存数据 state: { message: 'Hello!' }, // action 来控制 state 的改变——不直接去对 state 做改变,而是通过 action 来改变 setMessageAction (newValue) { // 因为都走action,就可以知道到底改变(mutation)是如何被触发的,出现错误,也可以记录记录日志啥的 this.state.message = newValue }, clearMessageAction () { this.state.message = '' } }
组件不允许直接修改属于 store 实例的 state,组件必须通过 action 来改变 state
也就是说,组件里面应该执行 action 来分发 (dispatch) 事件通知 store 去改变。这样约定的好处是:能够记录所有 store 中发生的 state 改变,同时实现能做到记录变更 (mutation)、保存状态快照、历史回滚/时光旅行的先进的调试工具。
Flux
Flux其实是一种思想,就像MVC,MVVM之类的,他给出了一些基本概念,所有的框架都可以根据他的思想来做一些实现。
Flux的最大特点就是数据都是单向流动的。
-
Dispatcher 的作用是接收所有的 Action,然后发给所有的 Store。这里的 Action 可能是 View 触发的,也有可能是其他地方触发的,比如测试用例。转发的话也不是转发给某个 Store,而是所有 Store。
-
Store 的改变只能通过 Action,不能通过其他方式。也就是说 Store 不应该有公开的 Setter,所有 Setter 都应该是私有的,只能有公开的 Getter。具体 Action 的处理逻辑一般放在 Store 里。
Flux 有一些缺点(特点),比如一个应用可以拥有多个 Store,多个Store之间可能有依赖关系;Store 封装了数据还有处理数据的逻辑。
redux
Redux使用一个对象存储整个应用的状态(global state),当global state发生变化时,状态从树形结构的最顶端往下传递。每一级都会去进行状态比较,从而达到更新。
action 可以理解为应用向 store 传递的数据信息(一般为用户交互信息)
dispatch(action) 是一个同步的过程:执行 reducer 更新 state -> 调用 store 的监听处理函数。
reducer 实际上就是一个函数:(previousState, action) => newState。用来执行根据指定 action 来更新 state 的逻辑。通过 combineReducers(reducers)可以把多个 reducer 合并成一个 root reducer。
reducer 不存储 state, reducer 函数逻辑中不应该直接改变 state 对象, 而是返回新的 state 对象(可以考虑使用 immutable-js)。
redux一些特性
-
Redux 里面只有一个 Store,整个应用的数据都在这个大 Store 里面。
-
Store 的 State 不能直接修改,每次只能返回一个新的 State。
-
Redux 整了一个 createStore 函数来生成 Store。
-
Store 允许使用 store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
-
-
Action 必须有一个 type 属性,代表 Action 的名称,其他可以设置一堆属性,作为参数供 State 变更时参考。
Redux 可以用 Action Creator 批量来生成一些 Action。
-
Reducer 纯函数来处理事件。Store 收到 Action 以后,必须给出一个新的 State(就是刚才说的Store 的 State 不能直接修改,每次只能返回一个新的 State),这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
-
Redux 里每一个 Reducer 负责维护 State 树里面的一部分数据
-
多个 Reducer 可以通过 combineReducers 方法合成一个根 Reducer,这个根 Reducer 负责维护整个 State
-
-
Redux 没有 Dispatcher 的概念,Store 里面已经集成了 dispatch 方法。store.dispatch()是 View 发出 Action 的唯一方法。
redux与flux对比
-
Flux 中 Store 是各自为战的,每个 Store 只对对应的 View 负责,每次更新都只通知对应的View
-
Redux 中各子 Reducer 都是由根 Reducer 统一管理的,每个子 Reducer 的变化都要经过根 Reducer 的整合
Redux则是一个纯粹的状态管理系统,react-redux是常规的状态管理系统(Redux)与React框架的结合版本——React利用React-Redux将它与React框架结合起来。React-Redux还有一些衍生项目,DVA就是一个基于对React-Redux进行封装并提供了一些优化特性的框架。
React-redux
Redux 和 Flux 类似,只是一种思想或者规范,它和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
但是因为 React 包含函数式的思想,也是单向数据流,和 Redux 很搭,所以一般都用 Redux 来进行状态管理。为了简单处理 Redux 和 React UI 的绑定,一般通过一个叫 react-redux 的库和 React 配合使用,这个是 react 官方出的
Redux将React组件分为容器型组件和展示型组件。
容器型组件一般通过connet函数生成,它订阅了全局状态的变化,通过mapStateToProps函数,我们可以对全局状态进行过滤,而展示型组件不直接从global state获取数据,其数据来源于父组件。
-
mapStateToProps 把容器组件的 state 映射到UI组件的 props
-
mapDispatchToProps 把UI组件的事件映射到 dispatch 方法
每一次全局状态发生变化,所有的容器型组件都会得到通知,而各个容器型组件需要通过shouldComponentUpdate函数来确实自己关注的局部状态是否发生变化、自身是否需要重新渲染,默认情况下,React组件的shouldComponentUpdate总返回true,这里貌似有一个严重的性能问题
Middleware(中间件)
在 Redux 中
-
同步的表现就是:Action 发出以后,Reducer 立即算出 State。
-
异步的表现就是:Action 发出以后,过一段时间再执行 Reducer——在 View 里发送 Action 的时候,加上一些异步操作了。
let next = store.dispatch;// 给原来的 dispatch 方法包裹了一层 store.dispatch = function dispatchAndLog(action) { // TODO next(action); }
单页面应用中充斥着大量的异步请求(ajax)。dispatch(action) 是同步的,如果要处理异步 action,需要使用一些中间件
Redux 提供了一个 applyMiddleware 方法来应用中间件:
const store = createStore( reducer, applyMiddleware(thunk, promise, logger) );
这个方法主要就是把所有的中间件组成一个数组,依次执行。也就是说,任何被发送到 store 的 action 现在都会经过thunk,promise,logger 这几个中间件了。
处理异步Action
用 Redux 处理异步,可以自己写中间件来处理,当然大多数人会选择一些现成的支持异步处理的中间件。比如 redux-thunk 或 redux-promise,分别是使用异步回调和 Promise 来解决异步 action 问题的。
-
thunk就是简单的action作为函数,在action进行异步操作,发出新的action。
缺点就是用户要写的代码有点多,可以看到上面的代码比较啰嗦
-
而promise只是在action中的payload作为一个promise,中间件内部进行处理之后,发出新的action。
和 redux-thunk 的思想类似,只不过做了一些简化,成功失败手动 dispatch 被封装成自动了:
**封装少,自由度高,但是代码就会变复杂;封装多,代码变简单了,但是自由度就会变差。**redux-thunk 和 redux-promise 刚好就是代表这两个面。
当业务逻辑多且复杂的时候会发生什么情况呢?我们的action越来越复杂,payload越来越长,当然我们可以分离开来单独建立文件去处理逻辑,但是实质上还是对redux的action和reducer进行了污染,让它们变得不纯粹了,action就应该作为一个信号,而不是处理逻辑,reducer里面处理要好一些,但是同样会生出几个多余的action类型进行处理,而且也只能是promise,不能做复杂的业务处理。
redux-saga
redux-saga是一个Redux中间件,用来帮你管理程序的副作用。或者更直接一点,主要是用来处理异步action。
redux-saga将进行异步处理的逻辑剥离出来,单独执行,利用generator实现异步处理。
关于saga原理的,推举阅读《前端技术栈(三):redux-saga,化异步为同步》
什么是Saga?
为了解决分布式系统中的LLT(Long Lived Transaction-长时运行事务的数据一致性)问题而提出的一个概念。比如网上购物下单后,需要等待付款才最终确定。
LLT拆成两个子事务,T1表示“确认订单||预订”事务,T2表示“发货”事务。如果在规定时间内付款的数据,才执行T2。其它的都回滚。
副作用(Side Effect)
side effect出自于“函数式编程”,这种编程范式鼓励我们多使用“纯函数”。显然,大多数的异步任务都需要和外部世界进行交互,不管是发起网络请求、访问本地文件或是数据库等等,因此,它们都会产生“副作用”。
纯函数特性
-
输出不受外部环境影响:同样的输入一定可以获得同样的输出
-
不影响外部环境:包括但不限于修改外部数据、发起网络请求、触发事件等等
为什么要多用纯函数呢?因为它们具有很强的“可预测性”。既然有纯函数,那肯定有不纯的函数喽,或者换个说法,叫做有“副作用”的函数。
redux-saga的优势
Redux 处理异步的中间件 redux-thunk 和 redux-promise,当然 redux 的异步中间件还有很多,他们可以处理大部分场景,这些中间件的思想基本上都是把异步请求部分放在了 action creator 中,理解起来比较简单。
redux-saga 采用了另外一种思路,它没有把异步操作放在 action creator 中,也没有去处理 reductor,而是把所有的异步操作看成“线程”,可以通过普通的action去触发它,当操作完成时也会触发action作为输出。saga 的意思本来就是一连串的事件。
redux-saga 把异步获取数据这类的操作都叫做副作用(Side Effect),它的目标就是把这些副作用管理好,让他们执行更高效,测试更简单,在处理故障时更容易。
Vuex
Vuex是专门为Vue设计的状态管理框架,同样基于Flux架构,并吸收了Redux的优点。
###### Redux
- 核心对象:store
- 数据存储:state
- 状态更新提交接口:==dispatch==
- 状态更新提交参数:带type和payload的==Action==
- 状态更新计算:==reducer==
- 限制:reducer必须是纯函数,不支持异步
- 特性:支持中间件
###### VUEX
- 核心对象:store
- 数据存储:state
- 状态更新提交接口:==commit==
- 状态更新提交参数:带type和payload的mutation==提交对象/参数==
- 状态更新计算:==mutation handler==
- 限制:mutation handler必须是非异步方法
- 特性:支持带缓存的getter,用于获取state经过某些计算后的值
Vuex相对于Redux的不同点有:
改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里直接改变state值即可(无需返回新的state)
尤大的说法:Redux 强制的 immutability,在保证了每一次状态变化都能追踪的情况下强制的 immutability 带来的收益很有限,为了同构而设计的 API 很繁琐,必须依赖第三方库才能相对高效率地获得状态树的局部状态,这些都是 Redux 不足的地方,所以也被 Vuex 舍掉了。
由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要修改State即可
Flux、Redux、Vuex 三个的思想都差不多,在具体细节上有一些差异,总的来说都是让 View 通过某种方式触发 Store 的事件或方法,Store 的事件或方法对 State 进行修改或返回一个新的 State,State 改变之后,View 发生响应式改变。
Vuex数据流的顺序是:
-
View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)
-
redux 推荐使用 Object.assign() 新建了一个副本,但是 Vue 定义每一个响应式数据的 ob 都是不可枚举的
Vuex异步action
mutation 都是同步事务,
对比Redux的中间件,Vuex 加入了 Action 这个东西来处理异步,Vuex的想法是把同步和异步拆分开,异步操作想咋搞咋搞,但是不要干扰了同步操作。View 通过 store.dispatch('increment') 来触发某个 Action,Action 里面不管执行多少异步操作,完事之后都通过 store.commit('increment') 来触发 mutation,一个 Action 里面可以触发多个 mutation。所以 Vuex 的Action 类似于一个灵活好用的中间件。
区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。
事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。
同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。
如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个点子一直没时间做,那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用的异步状态变化很有帮助。作者:尤雨溪 . 链接:https://www.zhihu.com/question/48759748/answer/112823337
Vuex 把同步和异步操作通过 mutation 和 Action 来分开处理,是一种方式。但不代表是唯一的方式,还有很多方式,比如就不用 Action,而是在应用内部调用异步请求,请求完毕直接 commit mutation,当然也可以。
Module
Vuex 引入了 Module 的概念,每个 Module 有自己的 state、mutation、action、getter,其实就是把一个大的 Store 拆开。
React-Redux vs VUEX 对比分析
和组件结合方式的差异
通过VUEX全局插件的使用,结合将store传入根实例的过程,就可以使得store对象在运行时存在于任何vue组件中。
而React-Redux则除了需要在较外层组件结构中使用<Provider/>以拿到store之外,还需要显式指定容器组件,即用connect包装一下该组件。这样看来我认为VUE是更推荐在使用了VUEX的框架中的每个组件内部都使用store,而React-Redux则提供了自由选择性。而VUEX即不需要使用外层组件,也不需要类似connect方式将组件做一次包装,我认为出发点应该是可能是为了避免啰嗦。
容器组件的差异
React-Redux提倡容器组件和表现组件分离的最佳实践,而VUEX框架下不做区分,全都是表现(展示)组件。我觉得不分优劣,React-Redux的做法更清晰、更具有强制性和规范性,而VUEX的方式更加简化和易于理解。
总的来说,就是谁包谁,谁插谁的问题。Redux毕竟是独立于React的状态管理,它与React的结合则需要对React组件进行一下外包装。而VUEX就是为VUE定制,作为插件、以及使用插入的方式就可以生效,而且提供了很大的灵活性。
参考文章
Vuex、Flux、Redux、Redux-saga、Dva、MobX https://juejin.im/post/5c18de8ef265da616413f332
react-redux与Vue-vuex的原理比较 https://www.yaruyi.com/article/redux-vuex
Vuex与Redux对比 https://blog.csdn.net/hyupeng1006/article/details/80755667
转载本站文章《单向数据流-从共享状态管理:flux/redux/vuex漫谈异步数据处理》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue/8440.html