引言:为何数据流管理如此重要?react的核心思想就是:UI=render(data),data就是咱们说的数据流,render是react提供的纯函数,因此用户界面的展现彻底取决于数据层。这篇文章但愿能用最浅显易懂的话,将react中的数据流管理,从自身到借助第三方库,将这些概念理清楚。我会列举几个当下最热的库,包括它们的思想以及优缺点,适用于哪些业务场景。这篇文章不是教程,不会讲如何去使用它们,更不会一言不合就搬源码,正如文章标题所说,只是浅谈,但愿读者在读完之后就算原先没有使用过这些库,也能大体有个思路,知道该如何选择性地深刻学习。前端
在本文正式开始以前,我先试图讲清楚两个概念,状态和数据:react
咱们都知道,react是利用可复用的组件来构建界面的,组件本质上是一个有限状态机,它可以记住当前所处的状态,而且可以根据不一样的状态变化作出不一样的操做。在react中,把这种状态定义为state,用来描述该组件对应的当前交互界面,表示当前界面展现的一种情况,react正是经过管理状态来实现对组件的管理,当state发生变动时,react会自动去执行相应的操做:绘制界面。web
因此状态是针对react component这种有限状态机才出现的名词,而数据就普遍了,它不光是指server层返回给前端的数据,react中的状态也是一种数据,当咱们改变数据的同时,就要经过改变状态去引起界面的变动;咱们真正要关心的是数据层的管理,咱们今天所讨论的数据流管理方案,特别是后面介绍的几种第三方库,不光是配合react,也能够配合其余的View框架(Vue、Angular等等),就比如开头提到的那个公式,引伸一下:UI = X(data),但今天主要是围绕react来说的,所以咱们在说react的状态管理其实和数据流管理是同样的,包括咱们会借助第三方库来帮助react管理状态,但愿不要有小伙伴太纠结于此。typescript
一,react自身的数据流管理方案express
咱们先来回顾一下,react自身是如何管理数据流的(也能够理解为如何管理应用状态):编程
react是自上而下的单向组件数据流,容器组件&展现组件(也叫傻瓜组件&聪明组件)是最经常使用的react组件设计方案,容器组件负责处理复杂的业务逻辑以及数据,展现组件负责处理UI层,一般咱们会将展现组件抽出来进行复用或者组件库的封装,容器组件自身经过state来管理状态,setState更新状态,从而更新UI,经过props将自身的state传递给展现组件实现通讯。redux
这是当业务需求不复杂,页面较简单时咱们经常使用的数据流处理方式,仅用react自身提供的props和state来管理足矣,可是若是稍微增长一点复杂度呢,好比当咱们项目中遇到这些问题:设计模式
1,如何实现跨组件通讯、状态同步以及状态共享?api
react V16.3之前,经过状态提高至最近的共同父组件来实现。(虽然有官方提供的context API,可是旧版本存在一个问题:看似跨组件,实则仍是逐级传递,若是中间组件使用了ShouldComponentUpdate检测到当前state和props没有变化,return false,那么context就会没法透传,所以context没有被官方推荐使用)。app
react V16.3版本之后,新版本context解决了以前的问题,能够轻松实现,但依然存在一个问题,context也是将底部子组件的状态控制交给到了顶级组件,可是顶级组件状态更新的时候必定会触发全部子组件的re-render,那么也会带来损耗。(虽然咱们能够经过一些手段来减小重绘,好比在中间组件的SCU里进行一些判断,可是当项目较大时,咱们须要花太多的精力去作这件事)
2,如何避免组件臃肿?
当某个组件的业务逻辑很是复杂时,咱们会发现代码越写越多,由于咱们只能在组件内部去控制数据流,没办法抽离,Model和View都放在了View层,整个组件显得臃肿不堪,业务逻辑通通堆在一块,难以维护。
3,如何让状态变得可预知,甚至可回溯?
当数据流混乱时,咱们一个执行动做可能会触发一系列的setState,咱们如何可以让整个数据流变得可“监控”,甚至能够更细致地去控制每一步数据或状态的变动?
4,如何处理异步数据流?
react自身并未提供多种处理异步数据流管理的方案,仅用一个setState已经很难知足一些复杂的异步流场景;
如何改进?
这个时候,咱们可能须要一个真正的数据流管理工具来帮助react了,咱们但愿它是真正脱离react组件的概念的,从UI层彻底抽离出来,只负责管理数据,让react只专一于View层的绘制,那这也是为何咱们须要使用那些第三方数据流管理工具的缘由,接下来咱们就来了解一些当前社区比较热门的数据流管理工具。
二,redux
我直接跳过了flux来讲redux,主要是由于redux是由flux演变而来,能够说是flux的升级增强版,flux具有的优点redux也作到了。
redux提供了哪些?
1,store:提供了一个全局的store变量,用来存储咱们但愿从组件内部抽离出去的那些公用的状态;
2,action:提供了一个普通对象,用来记录咱们每一次的状态变动,可日志打印与调试回溯,而且这是惟一的途径;
3,reducer:提供了一个纯函数,用来计算状态的变动;
为何须要redux?
不少人在用了一段时间的redux以后,最大的感想就是,redux要写大量的模板代码,很麻烦,还不如只用react来管理。特别是在react的新context推出之后,许多人更是直接弃用了redux,甚至以为redux已死。若是说旧版的context的弊端,咱们经过redux配合react-redux来实现跨组件的状态通讯同步等问题,那确实新版本的context能够替换掉这个功能点,但若是你的项目中仅仅是用redux作这些,那思考一下,你是否真的须要redux?也许从一开始你就不须要它。(虽然新版的context功能强大,可是依然是经过一个新的容器组件来替咱们管理状态,那么经过组件管理状态的问题依旧会存在,Consumer是和Provider一一对应的,在项目复杂度较高时,可能会出现多个Provider,更多个Consumer,甚至会一个Consumer须要对应多个Provider的传值等一系列复杂的状况,因此咱们依然要谨慎使用)
redux的核心竞争力
1,状态持久化:global store能够保证组件就算销毁了也依然保留以前状态;
2,状态可回溯:每一个action都会被序列化,Reducer不会修改原有状态,老是返回新状态,方便作状态回溯;
3,Functional Programming:使用纯函数,输出彻底依赖输入,没有任何反作用;
4,中间件:针对异步数据流,提供了类express中间件的模式,社区也出现了一大批优秀的第三方插件,可以更精细地控制数据的流动,对复杂的业务场景起到了缓冲地做用;
与其说是redux来帮助react管理状态,不如说是将react的部分状态移交至redux那里,redux彻头彻尾的纯函数理念就代表了它不会参与任何状态变化,彻底是由react本身来完成,只不过redux会提供一套工具,react照着说明书来操做罢了,因此这注定了想要接受redux,就必须按照它的规矩来作,除非你不肯意接受这种FP的模式。这种模式有利有弊,有利就是在一个大型的多人团队中,这种开发模式反而容易造成一种规约,让整个状态流程变得清晰,弊端就是对于小规模团队,尤为是着急发布上线的,这种繁重的代码模板无疑是一种负担。
redux的缺点:
1,繁重的代码模板:修改一个state可能要动四五个文件,可谓牵一发而动全身;
2,store里状态残留:多组件共用store里某个状态时要注意初始化清空问题;
3,无脑的发布订阅:每次dispatch一个action都会遍历全部的reducer,从新计算connect,这无疑是一种损耗;
4,交互频繁时会有卡顿:若是store较大时,且频繁地修改store,会明显看到页面卡顿;
5,不支持typescript;
关于如何优化,网上有不少优秀的案例,redux官方也提供了不少方法,这里再也不赘述。redux将来不会有太大的变化,那些弊端仍是会继续保留,可是这依然不会妨碍忠爱它的用户去使用它。
若是说redux那种强硬的函数式编程模式让不少人难以接受,那么当他们开始mobx的使用的时候,无疑是一种解脱。
三,mobx
最开始接触mobx也是由于redux做者Dan Abramov的那句:Unhappy with redux?try mobx,我相信不少人也是由于这句话而开始了解学习并使用它的。
下面列举一些mobx的优点(和redux进行一个对比)
1,redux不容许直接修改state,而mobx可随意修改;
2,redux修改状态必须走一套指定的流程较麻烦,而mobx能够在任何地方直接修改(非严格模式下);
3,redux模板代码文件多,而mobx很是简洁,就一个文件;
4,redux只有一个store,state or store难以取舍,而mobx多store,你能够把全部的state都放入store中,彻底交给mobx来管理,减小顾虑;
5,redux须要对监听的组件作SCU优化,减小重复render;而mobx都是Smart Component,不用咱们手动作SCU;
mobx的设计思想:
说了这么多,若是你是第一次了解mobx,是否是听着就感受很爽!没错,这就是mobx的魅力,那它是如何实现这些功能的呢?这里以mobx 5版本为例,实际上它是利用了ES6的proxy来追踪属性(旧版本是用Object.defineProperty来实现的)经过隐式订阅,自动追踪被监听的对象变化,而后触发组件的UI更新;若是说redux是把要作的事情都交给了用户,来保证本身的纯净,那么mobx就是把最简易的操做给了用户,其它的交给mobx内部去实现,用户没必要关心这个过程,Model和View彻底分离,咱们彻底能够将业务逻辑写在action里,用户只须要操做Observable data就好了,Observer view会自动作出响应,这就是mobx主打的响应式设计,可是编程风格依然是传统的面向对象的OO范式。(熟悉Vue的朋友必定对这种响应式设计不陌生,Vue就是利用了数据劫持来实现双向绑定,其实React + Mobx就是一个复杂点的Vue,Vue 3版本一个重大改变也是将代理交给了proxy)
刚刚mobx的优点说得比较多了,这边再总结一下:
1,代码量少;
2,基于数据劫持来实现精准定位(真正意义上的局部更新);
3,多store抽离业务逻辑(Model View分离);
4,响应式性能良好(频繁的交互依然能够胜任);
5,彻底能够替代react自身的状态管理;
6,支持typescript;
可是mobx真的这么完美吗,固然也有缺陷:
1,没有状态回溯能力:mobx是直接修改对象引用,因此很难去作状态回溯;(这点redux的优点就瞬间体现出来了)
2,没有中间件:和redux同样,mobx也没有很好地办法处理异步数据流,没办法更精细地去控制数据流动;(redux虽然本身不作,可是它提供了applyMiddleware!)
3,store太多:随着store数的增多,维护成本也会增长,并且多store之间的数据共享以及相互引用也会容易出错
4,反作用:mobx直接修改数据,和函数式编程模式强调的纯函数相反,这也致使了数据的不少未知性
其实如今主流的数据流管理分为两大派,一类是以redux为首的函数式库,还有一类是以mobx为首的响应式库,其实经过刚刚的介绍,咱们会发现,redux和mobx有一个共同的短板,那就是在处理异步数据流的时候,没有一个很好的解决方案,至少仅仅依靠自身比较吃力,那么接下来给你们介绍一个处理异步数据流的高手:rxjs。
四,rxjs
我相信不少人据说过rxjs学习曲线异常陡峭,是的,除了眼花缭乱的各种操做符(目前rxjs V6版本有120+个),关键是它要求咱们在处理事务的时候要贯彻“一切皆为流”的理念,更是让初学者难以理解。这一小节并不能让读者达到可以上手使用的程度,正如文章开头所说,但愿读者(新手)能对rxjs有一个新的认知,知道它是作什么的,它是如何实现的,它有哪些优点,咱们如何选择它,若是感兴趣还须要私下花大量时间去学习掌握各类操做符,但我也会尝试尽量地相对于前两个说得更细致一些。
在开始介绍rxjs以前,咱们先来简单地聊聊什么是响应式编程?我以一个很简单的小例子来看:a + b = c;若是站在传统的命令式编程的角度来看这段公式:c的值彻底依赖于a和b,这时候咱们去改变a的值,那咱们就须要再去手动计算a + b的值,a、b和c是相互依赖的;那么若是站在响应式编程的角度来看,这个公式又会变成这样:
c := a + b,a和b彻底不关心c的值,c也彻底不关心等式那边是a或者b,或者还有什么d,e,f。。。等式右边改变值了,左边会自动更改数值,这就是响应式编程的思惟方式。咱们再来看前端的框架历史,传统命令式编程的表明:jQuery,在过去咱们是如何绘制一个页面的?咱们会用jQuery提供的一套API,而后手动操做Dom来进行绘制,很精准,可是很累,由于彻底靠手动操做,且改动时性能损耗较大,开发者的注意力彻底在“如何去绘制”上面了;那咱们再来看响应式编程的react,它是如何来实现的?开发者根本不用关心界面如何绘制,只要告诉react咱们但愿页面长什么样子,就能够了,剩下的交给react,react就会自动帮咱们绘制界面,还记得开头时的那个核心思想吗:UI = render(data),咱们只要操做data就能够了,页面UI会自动做出响应,并且咱们一切的操做都是基于内存之中,不会有较大的性能损耗,这就是react响应式编程的精髓,也是为什么它叫做react。
回到咱们的rxjs上,rxjs是如何作到响应式的呢?多亏了它两种强大的设计模式:观察者模式和迭代器模式;简单地介绍一下:
1,观察者模式:
在观察者模式中,有两个重要的角色:Observable和Observer,熟悉mobx的同窗对这个必定不陌生(因此我建议想要学习rxjs的同窗,若是对mobx不熟悉,能够先学习一下mobx,而后再学习rxjs,这样会更容易理解一些),就是可观察对象和观察者,可观察对象(Observable)也就是事件发布者,负责产生事件,而观察者(Observer)也就是事件响应者,负责对发布的事件做出响应,可是如何链接一个发布者和响应者呢?经过订阅的形式,也就是subscribe方法(这也相似于redux的store.subscribe),而在订阅以前,他们二者是毫无关联的,不管Observable发出多少事件,Observer也不会作出任何响应,一样,当这种订阅关系中断时也不会;
2,迭代器模式:
在这里要先引出一个新的概念:拉取(pull)和推送(push),rxjs官方这两种协议有更详细的解释,我这里就直接引用一下:
拉取和推送实际上对于观察者来讲就是一个主动与被动的区别,是主动去获取仍是被动地接收。在rxjs中,做为事件响应者(消费者)的Observer对象也有一个next属性(回调函数),用来接收从发布者那里“推”过来的数据。(站在开发者的角度,咱们必定是但愿消息是被动地接收,由于咱们倡导的就是经过操做data数据层,让View层进行一个响应,那么这里data数据层必定是事件发布者,而View层就是事件响应者,每当data数据层发生变化时,都会主动推送一个值给View层,这才符合真正意义上的响应式编程,而rxjs作到了!)
如何配合react?
若是说redux和mobx的出现或多或少是由于react的存在,那么不一样的是rxjs和react并无什么关联,关于rxjs的历史这里很少说,感兴趣的能够了解一下Reactive Extension,rxjs只是响应式编程在JavaScript中的应用。那么如何帮助react实现状态管理呢,咱们只须要将组件做为事件响应者,而后在next回调里定义好更新组件状态的动做setState,当接收到数据推送时,就会自动触发setState,完成界面更新,这其实有点相似于mobx作的事情。(不少人在react项目中并无彻底只使用rxjs,而是用了这个redux-observable中间件,利用rxjs的操做符来处理异步action)
除了响应式编程的魅力,rxjs还有什么优点呢?
1,纯函数:rxjs中数据流动的过程当中,不会改变已经存在的Observable实例,会返回一个新的Observable,没有任何反作用;
2,强大的操做符:rxjs又被称为lodash for async,和lodash同样,拥有众多强大的操做符来操做数据流,不光是同步数据,特别是针对各类复杂的异步数据流,甚至能够多种事件流组合搭配,汇总到一块儿处理;
3,更独立:rxjs并不依赖于任何一个框架,它能够任意搭配,由于它的关注点彻底就是在于数据流的处理上,并且它更偏底层一些
那rxjs有哪些缺点呢?
1,学习曲线陡峭:光是这一点就已经让大多数人止步于此;
2,事件流高度抽象:用rxjs的用户反馈通常都是两种极端状况,用得好的都以为这个太厉害了,用得很差的都以为感受有点麻烦,增长了项目复杂度。
最后,总结一下各种的适用场景:
1,当咱们项目中复杂程度较低时,建议只用react就能够了;
2,当咱们项目中跨组件通讯、数据流同步等状况较多时,建议搭配react的新context api;
3,当项目复杂度通常时,小规模团队或开发周期较短时,建议使用mobx;
4,当项目复杂度较高时,团队规模较大或要求对事件分发处理可监控可回溯时,建议使用redux;
5,当项目复杂度较高,且数据流混杂时,建议使用rxjs;
结语:其实回顾全篇,我没有提到一个关键点是,各个库的性能对好比何。其实它们之间必定是有差别的,可是这点性能差别,相对于react自身组件设计不当而致使的性能损耗来讲,是能够忽略的,若是你如今的项目以为性能较差或者页面卡顿,建议先从react层面去考虑如何进行优化,而后再去考虑如何优化数据管理层。关于上面提到的三个数据流管理工具,有利有弊,针对弊端,网上也有一大批优秀的解决方案和改进,感兴趣的读者可自行查阅。
谢谢!
=============
ps:这篇文章会发布在携程公众号上,不是抄袭,我就是做者,先更新到博客园上而已。