专栏首页前端架构单向数据流-从共享状态管理:flux/redux/vuex漫谈异步数据处理
原创

单向数据流-从共享状态管理:flux/redux/vuex漫谈异步数据处理

不管是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出自于“函数式编程”,这种编程范式鼓励我们多使用“纯函数”。显然,大多数的异步任务都需要和外部世界进行交互,不管是发起网络请求、访问本地文件或是数据库等等,因此,它们都会产生“副作用”。

纯函数特性
  1. 输出不受外部环境影响:同样的输入一定可以获得同样的输出
  2. 不影响外部环境:包括但不限于修改外部数据、发起网络请求、触发事件等等

为什么要多用纯函数呢?因为它们具有很强的“可预测性”。既然有纯函数,那肯定有不纯的函数喽,或者换个说法,叫做有“副作用”的函数。

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

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Vuex

    在相对独立的组件中,action -> state -> view的单向数据流能得到保证。而真实业务场景经常需要状态传递及共享,一般方法是:

    ayqy贾杰
  • 状态管理的概念,都是纸老虎

    不管是Vue,还是 React,都需要管理状态(state),比如组件之间都有共享状态的需要。什么是共享状态?比如一个组件需要使用另一个组件的状态,或者一个组件...

    Nealyang
  • 浅谈前端状态管理

    近两年前端技术的发展如火如荼,大量的前端项目都在使用或转向 Vue 和 React 的阵营,由前端渲染页面的单页应用占比也越来越高,这就代表前端工作的复杂度也在...

    疯狂的技术宅
  • Redux/Mobx/Akita/Vuex对比 - 选择更适合低代码场景的状态管理方案

    近期准备开发一个数据分析 SDK,定位是作为数据中台向外输出数据分析能力的载体,前端的功能表现类似低代码平台的各种拖拉拽。作为中台能力的载体,SDK 未来很大概...

    寒月十八
  • Vue 应用单元测试的策略与实践 04 - Vuex 单元测试

    2.2 在 Vue 应用的单元测试中,对 Vuex store 该如何测试?如何测试与 Vue 组件之间的交互?

    JimmyLv_吕靖
  • MobX

    也就是说,只要知道哪些东西是状态相关的(源于应用状态),在状态发生变化时,就应该自动完成状态相关的所有事情,自动更新UI,自动缓存数据,自动通知server

    ayqy贾杰
  • 前端:从状态管理到有限状态机的思考

    在使用类似Vue,React框架时,我们一定会使用状态管理吗?这个答案是肯定的。或许我不会主动去使用Vuex, Redux,但我们编写每一个组件的时候就已经在管...

    coder_koala
  • 数据管理工具Flux、Redux、Vuex的区别

    组件式开发的核心思路是MVC,Model层的数据发生变化,驱动View层的视图发生变化。试想一个场景,如果ModelA触发ModelB变化,导致ViewB发生变...

    用户1217459
  • Vuex使用场景

    最近本着学习的心态,拉取了公司大佬最新架构的vue项目好好研究一番。然而在vuex的使用上本人出现了很大的歧义。到底什么场景使用vuex?于是搜集了一些资料,但...

    wade
  • vuex入门学习笔记

    我们需要解决多个组件间的数据通信和状态管理就显得难以维护的问题,在vue中用的是vuex,在react中用的是redux.通过本篇教程将基本熟悉它的所有常用用法...

    RobinsonZhang
  • 2021前端react面试题汇总

    (2)区别 Redux更多的是遵循Flux模式的一种实现,是一个 JavaScript库,它关注点主要是以下几方面∶

    it优课
  • Redux

    MVC中,数据(Model)、表现层(View)、逻辑(Controller)之间有明确的界限,但数据流是双向的,在大型应用中尤其明显。一个变化(用户输入或者内...

    ayqy贾杰
  • 数据流管理方案 | Redux 和 MobX 哪个更好?

    面试中常问的一道问题就是“你了解哪些数据流管理方案”,面对这样的提问,先搞懂为什么要学数据流管理,再来梳理、对比你所知道的方案。真正的前端开发,不仅仅要面试造火...

    用户3806669
  • 「首席架构师推荐」React生态系统大集合

    首席架构师智库
  • 干货 | 浅谈React数据流管理

    颜陈宇,携程玩乐高级前端开发工程师,前端架构组成员,目前主要负责玩乐国际化项目的App、H5以及Online三端技术架构。热衷于react技术栈,喜欢阅读和分享...

    携程技术
  • React进阶(1)-理解Redux

    在React中,数据流是单向的,并且是不可逆的,这其实,也很好理解,之所以这么设计,是因为组件复用的特点

    itclanCoder
  • weex官方demo weex-hackernews代码解读(上)

    一、介绍 weex 是阿里出品的一个类似RN的框架,可以使用前端技术来开发移动应用,实现一份代码支持H5,IOS和Android。最新版本的weex已默认将v...

    JadePeng
  • React进阶(1)-理解Redux

    在React中,数据流是单向的,并且是不可逆的,这其实,也很好理解,之所以这么设计,是因为组件复用的特点

    itclanCoder
  • React组件设计实践总结05 - 状态管理

    今天是 520,这是本系列最后一篇文章,主要涵盖 React 状态管理的相关方案。

    _sx_

扫码关注云+社区

领取腾讯云代金券