深入理解redux数据流和异步过程管理

作者&投稿:宰泄 (若有异议请与网页底部的电邮联系)
~ 前端框架的数据流

前端框架实现了数据驱动视图变化的功能,我们用template或者jsx描述好了数据和视图的绑定关系,然后就只需要关心数据的管理了。

数据在组件和组件之间、组件和全局store之间传递,叫做前端框架的数据流。

一般来说,除了某部分状态数据是只有某个组件关心的,我们会把状态数据放在组件内以外,业务数据、多个组件关心的状态数据都会放在store里面。组件从store中取数据,当交互的时候去通知store改变对应的数据。

这个store不一定是Redux、mobox这些第三方库,其实react内置的context也可以作为store。但是context做为store有一个问题,任何组件都能从context中取出数据来修改,那么当排查问题的时候就特别困难,因为并不知道是哪个组件把数据改坏的,也就是数据流不清晰。

正是因为这个原因,我们几乎见不到用context作为store,基本都是搭配一个redux。

所以为什么redux好呢?第一个原因就是数据流清晰,改变数据有统一的入口。

组件里都是通过dispatch一个action来触发store的修改,而且修改的逻辑都是在reducer里面,组件再监听store的数据变化,从中取出最新的数据。

这样数据流动是单向的,清晰的,很容易管理。

这就像为什么我们在公司里想要什么权限都要走审批流,而不是直接找某人,一样的道理。集中管理流程比较清晰,而且还可以追溯。

异步过程的管理

很多情况下改变store数据都是一个异步的过程,比如等待网络请求返回数据、定时改变数据、等待某个事件来改变数据等,那这些异步过程的代码放在哪里呢?

组件?

放在组件里是可以,但是异步过程怎么跨组件复用?多个异步过程之间怎么做串行、并行等控制?

所以当异步过程比较多,而且异步过程与异步过程之间也不独立,有串行、并行、甚至更复杂的关系的时候,直接把异步逻辑放组件内不行。

不放组件内,那放哪呢?

redux提供的中间件机制是不是可以用来放这些异步过程呢?

redux中间件

先看下什么是redux中间件:

redux的流程很简单,就是dispatch一个action到store,reducer来处理action。那么如果想在到达store之前多做一些处理呢?在哪里加?

改造dispatch!中间件的原理就是层层包装dispatch。

下面是applyMiddleware的源码,可以看到applyMiddleware就是对store.dispatch做了层层包装,最后返回修改了dispatch之后的store。

functionapplyMiddleware(middlewares){letdispatch=store.dispatchmiddlewares.forEach(middleware=>dispatch=middleware(store)(dispatch))return{...store,dispatch}}

所以说中间件最终返回的函数就是处理action的dispatch:

functionmiddlewareXxx(store){returnfunction(next){returnfunction(action){//xx};};};}

中间件会包装dispatch,而dispatch就是把action传给store的,所以中间件自然可以拿到action、拿到store,还有被包装的dispatch,也就是next。

比如redux-thunk中间件的实现:

functioncreateThunkMiddleware(extraArgument){return({dispatch,getState})=>next=>action=>{if(typeofaction==='function'){returnaction(dispatch,getState,extraArgument);}returnnext(action);};}constthunk=createThunkMiddleware();

它判断了如果action是一个函数,就执行该函数,并且把store.dispath和store.getState传进去,否则传给内层的dispatch。

通过redux-thunk中间件,我们可以把异步过程通过函数的形式放在dispatch的参数里:

constlogin=(userName)=>(dispatch)=>{dispatch({type:'loginStart'})request.post('/api/login',{data:userName},()=>{dispatch({type:'loginSuccess',payload:userName})})}store.dispatch(login('guang'))

但是这样解决了组件里的异步过程不好复用、多个异步过程之间不好做并行、串行等控制的问题了么?

没有,这段逻辑依然是在组件里写,只不过移到了dispatch里,也没有提供多个异步过程的管理机制。

解决这个问题,需要用redux-saga或redux-observable中间件。

redux-saga

redux-saga并没有改变action,它会把action透传给store,只是多加了一条异步过程的处理。

redux-saga中间件是这样启用的:

import{createStore,applyMiddleware}from'redux'importcreateSagaMiddlewarefrom'redux-saga'importrootReducerfrom'./reducer'importrootSagafrom'./sagas'constsagaMiddleware=createSagaMiddleware()conststore=createStore(rootReducer,{},applyMiddleware(sagaMiddleware))sagaMiddleware.run(rootSaga)

要调用run把saga的watchersaga跑起来:

watchersaga里面监听了一些action,然后调用workersaga来处理:

import{all,takeLatest}from'redux-saga/effects'function*rootSaga(){yieldall([takeLatest('login',login),takeLatest('logout',logout)])}exportdefaultrootSaga

redux-saga会先把action透传给store,然后判断下该action是否是被taker监听的:

functionsagaMiddleware({getState,dispatch}){returnfunction(next){returnfunction(action){constresult=next(action);//把action透传给storechannel.put(action);//触发saga的action监听流程returnresult;}}}

当发现该action是被监听的,那么就执行相应的taker,调用workersaga来处理:

function*login(action){try{constloginInfo=yieldcall(loginService,action.account)yieldput({type:'loginSuccess',loginInfo})}catch(error){yieldput({type:'loginError',error})}}function*logout(){yieldput({type:'logoutSuccess'})}

比如login和logout会有不同的workersaga。

login会请求login接口,然后触发loginSuccess或者loginError的action。

logout会触发logoutSuccess的action。

reduxsaga的异步过程管理就是这样的:先把action透传给store,然后判断action是否是被taker监听的,如果是,则调用对应的workersaga进行处理。

reduxsaga在redux的action流程之外,加了一条监听action的异步处理的流程。

其实整个流程还是比较容易理解的。理解成本高一点的就是generator的写法了:

比如下面这段代码:

function*xxxSaga(){while(true){yieldtake('xxx_action');//...}}

它就是对每一个监听到的xxx_action做同样的处理的意思,相当于takeEvery:

function*xxxSaga(){yieldtakeEvery('xxx_action');//...}

但是因为有一个while(true),很多同学就不理解了,这不是死循环了么?

不是的。generator执行后返回的是一个iterator,需要另外一个程序调用next方法才会继续执行。所以怎么执行、是否继续执行都是由另一个程序控制的。

在redux-saga里面,控制workersaga执行的程序叫做task。workersaga只是告诉了task应该做什么处理,通过call、fork、put这些命令(这些命令叫做effect)。

然后task会调用不同的实现函数来执行该workersaga。

为什么要这样设计呢?直接执行不就行了,为啥要拆成workersaga和task两部分,这样理解成本不就高了么?

确实,设计成generator的形式会增加理解成本,但是换来的是可测试性。因为各种副作用,比如网络请求、dispatchaction到store等等,都变成了call、put等effect,由task部分控制执行。那么具体怎么执行的就可以随意的切换了,这样测试的时候只需要模拟传入对应的数据,就可以测试workersaga了。

reduxsaga设计成generator的形式是一种学习成本和可测试性的权衡。

还记得redux-thunk有啥问题么?多个异步过程之间的并行、串行的复杂关系没法处理。那redux-saga是怎么解决的呢?

redux-saga提供了all、race、takeEvery、takeLatest等effect来指定多个异步过程的关系:

比如takeEvery会对多个action的每一个做同样的处理,takeLatest会对多个action的最后一个做处理,race会只返回最快的那个异步过程的结果,等等。

这些控制多个异步过程之间关系的effect正是redux-thunk所没有的,也是复杂异步过程的管理必不可少的部分。

所以redux-saga可以做复杂异步过程的管理,而且具有很好的可测试性。

其实异步过程的管理,最出名的是rxjs,而redux-observable就是基于rxjs实现的,它也是一种复杂异步过程管理的方案。

redux-observable

redux-observable用起来和redux-saga特别像,比如启用插件的部分:

functionmiddlewareXxx(store){returnfunction(next){returnfunction(action){//xx};};};}0

和reduxsaga的启动流程是一样的,只是不叫saga而叫epic。

但是对异步过程的处理,reduxsaga是自己提供了一些effect,而redux-observable是利用了rxjs的operator:

functionmiddlewareXxx(store){returnfunction(next){returnfunction(action){//xx};};};}1

通过ofType来指定监听的action,处理结束返回action传递给store。

相比redux-saga来说,redux-observable支持的异步过程的处理更丰富,直接对接了operator的生态,是开放的,而redux-saga则只是提供了内置的几个effect来处理。

所以做特别复杂的异步流程处理的时候,redux-observable能够利用rxjs的操作符的优势会更明显。

但是redux-saga的优点还有基于generator的良好的可测试性,而且大多数场景下,redux-saga提供的异步过程的处理能力就足够了,所以相对来说,redux-saga用的更多一些。

总结

前端框架实现了数据到视图的绑定,我们只需要关心数据流就可以了。

相比context的混乱的数据流,redux的view->action->store->view的单向数据流更清晰且容易管理。

前端代码中有很多异步过程,这些异步过程之间可能有串行、并行甚至更复杂的关系,放在组件里并不好管理,可以放在redux的中间件里。

redux的中间件就是对dispatch的层层包装,比如redux-thunk就是判断了下action是function就执行下,否则就是继续dispatch。

redux-thunk并没有提供多个异步过程管理的机制,复杂异步过程的管理还是得用redux-saga或者redux-observable。

redux-saga透传了action到store,并且监听action执行相应的异步过程。异步过程的描述使用generator的形式,好处是可测试性。比如通过take、takeEvery、takeLatest来监听action,然后执行workersaga。workersaga可以用put、call、fork等effect来描述不同的副作用,由task负责执行。

redux-observable同样监听了action执行相应的异步过程,但是是基于rxjs的operator,相比saga来说,异步过程的管理功能更强大。

不管是redux-saga通过generator来组织异步过程,通过内置effect来处理多个异步过程之间的关系,还是redux-observable通过rxjs的operator来组织异步过程和多个异步过程之间的关系。它们都解决了复杂异步过程的处理的问题,可以根据场景的复杂度灵活选用。

作者:zxg_神说要有光




react会到什么程度就可以进公司做活
因为react只是一个写网页的框架,所以最基本的web前端知识,肯定也要了解,比如HTML基础知识,JS基础知识,CSS,CSS3,AJAX等等...会redux,深入理解redux的action,reducer,store,中间件。在理解redux的同时,能会immutable.js那就更好了。然后再加深,就是要知道如如何对react代码进行优化,性能进行优化...

react为什么性能比vue好(react比vue简单)
使用react和vue,主要是理解他们的设计思路的不同。 react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流,推崇...react整体的思路就是函数式,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以做到,比如结合redux-form,而vue是基于可变数据的,支持双向绑定。

哪位大佬有 React进阶之路,这个教材百度网盘的链接有吗?急!
Router、Redux和MobX 3个React技术栈的重要成员,并通过实战项目讲解这些技术如何和React结合使用。《React进阶之路》示例丰富、注重实战,适用于从零开始学习React的初学者,或者已经有一些React使用经验,但希望更加全面、深入理解React技术栈的开发人员。阅读本书,需要先掌握基础的前端开发知识。

如何学习web3?
2.掌握移动端开发原理,理解原生开发和混合开发。 3.熟练使用react-native和Flutter框架完成移动端开发。 4.掌握微信小程序以及了解支付宝小程序的开发。 5.完成大型电商项目开发。 知识点: 1、React面向组件编程、表单数据、组件通信、监听、声明周期、路由、Redux基本概念。练使用react完成项目开发、掌握Redux中的异步...

vue的hook能将同一功能的代码单独抽成1个方法吗?
Q1: 如何看待redux我不会推荐任何一个库,要看使用场景吧。比如针对数据和缓存的库,就该用对应的库Q2: 很多开发者认为react的入门很难,有没有什么快速上...如果有人觉得入门很难的话,可能就意味着他们不知道怎么去做一个单页的应用程序,可能不是react本身的问题,但他们会认为是react难以理解的问题。可以先把...

整理涵盖很全很广的前端知识点
11、对高阶组件的理解高阶组件是参数为组件,返回值为新组件的函数。 HOC 是纯函数,没有副作用。 HOC 在 React 的第三方库中很常见,例如 Redux 的 connect 组件。高阶组件的作用:12、可以用哪些方式创建 React 组件?React.createClass()、ES6 class 和无状态函数13、 React 元素与组件的区别?

还在和Redux对线?是时候体验下Zustand的高效与简洁了
Redux的局限性,尤其是在状态管理和React集成方面,使得开发者在使用过程中感到繁琐和困扰。首先,Redux的全局状态管理插件特性,通过`connect`实现状态绑定,导致状态与视图的关联性混乱,不利于状态的复用。其次,`Actions`和`ActionTypes`的使用在弱类型语言环境下显得冗余,不同文件夹间的分散存储加重了...

一个前端vue学的还不熟练,拿到offer的公司用的react框架。说是给我培 ...
理念上这两个东西都非常相似。react不太友好的是东西非常多,vue在这方面比较好,什么router,vuex等等官方都有。react这边杂七杂八的(熟悉以后都不是问题,前期吃力点),router还好,但是状态管理有redux、mobx、soga等等。另外还有umijs这种规范和组织类的。都是需要花时间去理解。

咋深入Redux-Saga,要不手写一个
这期分享一下redux-saga源码实现,即使以前完全没用过redux-saga也没关系,我会先从如何使用讲起,然后再逐步替换redux-saga提供的常用api。 redux-saga自己用Generator实现了一套异步流程管理,所以如果不了解Generator的可以先了解一下 Redux-Saga源码理解还是优点难度的 我们先从小demo来了解redux-saga的使用constdispatch...

小菜鸟如何系统性学习react?
使得状态更新更加高效、易于理解。学习Redux不仅能提升您的开发能力,还能帮助您构建更加稳定、可扩展的应用。完成这些官方示例的学习后,您可以尝试动手实践,从简单的项目开始,逐步深入,不断积累经验。通过实践,您将更好地理解和运用React的各种特性,最终成为一名熟练的React开发者。

四子王旗18499312147: 前端都学什么内容呀?
游趴山玫: 这里根据行业变化和企业用人需求整理了一份web前端系统全面的学习路线,主要学习以下内容:第一阶段:专业核心基础阶段目标:1. 熟练掌握HTML5、CSS3、Less、Sass、响应书布局、移动端开发.2. 熟练运用HTML+CSS特性完成页面...

四子王旗18499312147: - 如何提高小学生语文课堂学习兴趣的论文 -
游趴山玫: 摘要提高语文课堂教学的质量,首要是使学生全身心投入到学习中,积极主动地参与课堂教学活动.因此必须要培养学生的学习兴趣.在教学过程中可以通过巧设导入;善设问题;教学...

四子王旗18499312147: linux 管道原理?
游趴山玫: 左边不的命令执行的结果 作为右边命令输出的结果

四子王旗18499312147: 什么是手机的UART设制?
游趴山玫:UART =Universal Asynchronous Receiver/Transmitter 通用非同步收发传输器 UART: Universal Asynchronous Receiver/Transmitter,通用异步接收/发送装置,UART是一个并行输入成为串行输出的芯片,通常集成在主板上,多数是16550AFN芯...

四子王旗18499312147: 有没有人了解人工智能这一方面的课程的,人工智能培训主要都哪些课程? -
游趴山玫: 人工智能学习主要分5大阶段:阶段一:数据分析师认知篇 菜鸟筑基,即代码级.将主要侧重Python语言及数据分析包的学习.通过对Python语言、Python数据处理、分析包及可视化包的学习,训练学员掌握人工智能必备的基本编码能力,为后...

四子王旗18499312147: UART是什么意思? -
游趴山玫: UART KK: [] DJ: [] abbr.1. =Universal Asynchronous Receiver/Transmitter 通用非同步收发传输器 UART 开放分类: 计算机、通信、信息 UART: Universal Asynchronous Receiver/Transmitter,通用异步接收/发送装置,UART是一个并行输入成为...

四子王旗18499312147: 说明文的方法以及方法的作用 -
游趴山玫: ①、举例子:通过举具体的实例对事物的特征/事理加以说明,从而使说明更具体,更有说服力. ②、分类别:对事物的特征/事理分门别类加以说明,使说明更有条理性. ③、作比较...

四子王旗18499312147: 想学习Linux 驱动,需要什么知识 -
游趴山玫: 首先,接触linux操作系统,在你的电脑上装一个linxu操作系统(建议ubuntu,比 较友好),熟悉经常要用的命令,熟悉环境(建议看“鸟哥的linux私房菜”).其次,阅读经典书籍是不可少的,建议先看ldd前四章,大概了解linux驱动的框架 ,...

四子王旗18499312147: 数据结构在软件开发中的作用 -
游趴山玫: 本人还是自称为菜鸟好了,学了一点相关知识,谈不上指点,心得倒是马马虎虎,见笑 数据结构和算法设计是分不开的(大学课程里面这是整个的一门课).对于软件初学者,这部分内容不是特别重要,因为很自然,新手总是喜欢用简单、好理...

四子王旗18499312147: 通信端口UART指的是什么?
游趴山玫: UART通信 UART: Universal Asynchronous Receiver/Transmitter,通用异步接收/发送装置,UART是一个并行输入成为串行输出的芯片,通常集成在主板上,多数是16550AFN芯片.因为计算机内部采用并行数据数据,不能直接把数据发到...

本站内容来自于网友发表,不代表本站立场,仅表示其个人看法,不对其真实性、正确性、有效性作任何的担保
相关事宜请发邮件给我们
© 星空见康网