单向数据流-从共享状态管理: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出自于“函数式编程”,这种编程范式鼓励我们多使用“纯函数”。显然,大多数的异步任务都需要和外部世界进行交互,不管是发起网络请求、访问本地文件或是数据库等等,因此,它们都会产生“副作用”。
纯函数特性
输出不受外部环境影响:同样的输入一定可以获得同样的输出
不影响外部环境:包括但不限于修改外部数据、发起网络请求、触发事件等等
为什么要多用纯函数呢?因为它们具有很强的“可预测性”。既然有纯函数,那肯定有不纯的函数喽,或者换个说法,叫做有“副作用”的函数。
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
单向数据流-从共享状态管理:flux/redux/vuex漫谈异步数据处理的更多相关文章
- 状态管理工具对比vuex、redux、flux
1.为什么要使用状态管路工具 在跨层级的组件之间传递信息,尤其是复杂的组件会非常困难.也不利于开发和维护,这时我们就a需要用到状态管理工具. 2.Flux
- [Full-stack] 状态管理技巧 - Redux
资源一: In React JS Tutorials, lectures from 9. From: React高级篇(一)从Flux到Redux,react-redux 从Flux到Redux,再到 ...
- 网页前端状态管理库Redux学习笔记(一)
最近在博客园上看到关于redux的博文,于是去了解了一下. 这个Js库的思路还是很好的,禁止随意修改状态,只能通过触发事件来修改.中文文档在这里. 前面都很顺利,但是看到异步章节,感觉关于异步说得很乱 ...
- React状态管理之redux
其实和vue对应的vuex都是差不多的东西,这里稍微提一下(安装Redux略过): import { createStore, combineReducers, applyMiddleware } f ...
- 状态管理之 Flux、Redux、Vuex、MobX(概念篇)
本文是对 Flux.Redux.Vuex.MobX 几种常用状态管理模式的总结,偏向于概念层面,不涉及过多代码. 状态管理 什么是状态管理? 状态管理就是,把组件之间需要共享的状态抽取出来,遵循特定的 ...
- 微信小程序里使用 Redux 状态管理
微信小程序里使用 Redux 状态管理 前言 前阵子一直在做小程序开发,采用的是官方给的框架 wepy , 如果还不了解的同学可以去他的官网查阅相关资料学习:不得不说的是,这个框架确相比于传统小程序开 ...
- React 新 Context API 在前端状态管理的实践
本文转载至:今日头条技术博客 众所周知,React的单向数据流模式导致状态只能一级一级的由父组件传递到子组件,在大中型应用中较为繁琐不好管理,通常我们需要使用Redux来帮助我们进行管理,然而随着Re ...
- vue总结 08状态管理vuex
状态管理 类 Flux 状态管理的官方实现 由于状态零散地分布在许多组件和组件之间的交互中,大型应用复杂度也经常逐渐增长.为了解决这个问题,Vue 提供 vuex:我们有受到 Elm 启发的状态管 ...
- 为管理复杂组件状态困扰?试试 vue 简单状态管理 Store 模式【转】
https://juejin.im/post/5cd50849f265da03a54c3877 在 vue 中,通信有几种形式: 父子组件 emit/on vuex 中共享 state 跨组件 Eve ...
- 纯粹极简的react状态管理组件unstated
简介 unstated是一个极简的状态管理组件 看它的简介:State so simple, it goes without saying 对比 对比redux: 更加灵活(相对的缺点是缺少规则,需要 ...
随机推荐
- 神经网络基础篇:Python 中的广播(Broadcasting in Python)
Python 中的广播 这是一个不同食物(每100g)中不同营养成分的卡路里含量表格,表格为3行4列,列表示不同的食物种类,从左至右依次为苹果,牛肉,鸡蛋,土豆.行表示不同的营养成分,从上到下依次为碳 ...
- FDA周五发布的药物安全警示信息相对会较少地被媒体传播
The Friday Effect: Firm Lobbying, the Timing of Drug Safety Alerts, and Drug Side Effects 周五发布的药物安全警 ...
- 2007年对Youtube小视频的分析文章
Understanding the Characteristics of Internet Short Video Sharing: YouTube as a Case Study 视频的种类 该研究 ...
- el-table 多表格弹窗嵌套数据显示异常错乱问题
1.业务背景 使用vue+element开发报表功能时,需要列表上某列的超链接按钮弹窗展示,在弹窗的el-table列表某列中再次使用超链接按钮点开弹窗,以此类推多表格弹窗嵌套,本文以弹窗两次为例 最 ...
- ReverseMe-120
一道好题,没解出来但是收获很多 贴两位大牛的题解 [精选]攻防世界逆向高手题之ReverseMe-120-CSDN博客 攻防世界ReverseMe-120详解_攻防世界reverseme基本思路-CS ...
- 27. 干货系列从零用Rust编写正反向代理,Rust中日志库的应用基础准备
wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现 ...
- 排序:使数组唯一的最小增量 (3.22 leetcode每日打卡)
给定整数数组 A,每次 move 操作将会选择任意 A[i],并将其递增 1. 返回使 A 中的每个值都是唯一的最少操作次数. 示例 1: 输入:[1,2,2]输出:1解释:经过一次 move 操作, ...
- 字节跳动今日头条-抖音小程序序html富文本显示解决办法
我所知道的,目前很多微信小程序开发者大都使用了"wxParse"的一个小程序端富文本解析代码,但对于开发抖音.今日头条小程序的人来说,貌似官方或者第三方也没有出一个解决html富文 ...
- C#简化工作之实现网页爬虫获取数据
公众号「DotNet学习交流」,分享学习DotNet的点滴. 1.需求 想要获取网站上所有的气象信息,网站如下所示: 目前总共有67页,随便点开一个如下所示: 需要获取所有天气数据,如果靠一个个点开再 ...
- C++ Qt开发:Qt的安装与配置
Qt是一种C++编程框架,用于构建图形用户界面(GUI)应用程序和嵌入式系统.Qt由Qt公司(前身为Nokia)开发,提供了一套跨平台的工具和类库,使开发者能够轻松地创建高效.美观.可扩展的应用程序. ...