资讯专栏INFORMATION COLUMN

Flux用过了,Redux也用过了,还是觉得不顺手?要不要自己造一个?

李世赞 / 2371人阅读

摘要:前言很多同学用过了,也用过了,但还是觉得不称心要不要自己造一个一百行来代码就基本搞定,其实,自己造的框架实不实用,并不重要,重要的是思想。总线根据路由表,调用对应的进行处理。

1 前言

很多同学用过了Flux,也用过了Redux,但还是觉得不称心?要不要自己造一个?一百行来代码就基本搞定,So easy, so good!

其实,自己造的框架实不实用,并不重要,重要的是思想。有了设计框架的思想后,再去看人家的框架,就会更多地关注人家为何要这么设计?好处在哪?弊端在哪?是否有改进的地方?明白了框架设计者的想法,才能更好地使用框架。

现在,咱们就一起来设计一个React框架,这个框架具备以下几个的特点:

单向数据流:业务数据从UI层触发,经处理到Module层便结束,不再需要人为地将数据反映到UI层。

消息机制:组件与服务之间通过消息总线完成,包括组件与组件之间的嵌套关系。

咱们给这个框架起个响亮的名字——Rebus(React-Bus)。这里的Bus不是公交车的Bus,是计算机基础原理中“Bus”(总线)。很显然,咱们要用“消息总线”这样的思想,实现ReactJs的单向数据流开发模式。一句话概括咱们的框架:Rebus是一个基于消息总线的,单向数据流的,ReactJs开发框架

这里是用Rebus写的一个TodoMVC实例:https://github.com/odebo/todomvc-rebus(看在我的代码写得如此粗糙的份上,大虾们赏颗星星鼓励鼓励下呗)。

2 什么是单向数据流模式

什么是“单向数据流模式”?这个概念对很多人来说可能有点陌生。下面是Facebook的Flux官网(http://facebook.github.io/flux/)提供的说明图:

好像有点抽象?那咱们先补补脑,看看什么是双向数据流模式。

什么是双向数据模式?简单地说就是UI层的一个操作经过UI层(View)、控制层(Control)、模式层(Model),做完增、删、改、查等处理后,还得反过来,手动地将增删改查后的数据反映到UI层上。这就双向数据流模式。

而Flux中所谓的单向数据流模式是指:UI层监听应用的“状态”,当一个操作(Action)经过Dispatch(分发器)、Store(状态容器),最后更新了“状态”,UI层自动根据“状态”的变化而更新界面。

这里的“状态”是指一个应用某个时刻的某个状态:比如左侧菜单栏展开与否——状态;导航中高亮项是谁——状态;用户是否登录,用户是谁——状态;Table中多少个Item,分别是什么内容——状态。

简单地说,单向数据流就是单向绑定,UI层与状态绑定,当状态发生变化,UI层自动更新。

可能有的同学会问,既然有AngularJs这样的双向绑定的MVVM模式,还搞什么单向绑定模式,听起来弱爆了。双向绑定肯定比单向绑定高大上得多。

这个问题不太好下结论,双向绑定固然有双向绑定的好处,但也有它的弊端。而相比单向数据流的逻辑处理思路更加单纯清晰。

3 Rebus 框架的数据流模型

理解了单向数据流后,咱们给出Rebus框架的数据流模式(如下图)。概括起来就三个步骤:

UI层触发的一个Action。

Rebus总线根据Action路由表选择对应的Service进行处理。

Service处理后,更新状态(State),结束。

这里的Services层指的是业务服务层,提供业务处理接口,包括对状态的修改,对后台数据的异步处理等等。如果觉得这一层太厚,可以分离出专门的Modle接口层。但不管怎样,一个业务操作从UI层到最后修改状态便结束,数据流方向只有一个。

但光这么说还是太抽象了,咱们直接上代码,看看在TodoMVC这个例子中,添加一个新的Todo这个操作是怎么被处理的。

是不是挺简单,简洁的三层结构,清晰的数据流:

ReactJs组件只负责渲染和触发Action,具体谁来响应Action,它不管。

Rebus总线根据Action路由表,调用对应的Service进行处理。

Service层进行完逻辑处理后,通过Rebus.setState()方法更新状态。

但你一定会问:React组件是怎么监听状态的变化的?其实很简单,直接看代码:比如咱们希望添加新的Todo后,TodoBody组件会自动更新。所以TodoBody组件应该监听状态“todos”的变化。

4 Rebus中的action

用过Flux的同学都知道Flux中有个叫Dispatch的模块,用来dispatch各种Action。而咱们的Rebus.execute()的作用与Dispatch.dispatch()差不多(如下图)。

不一样的是Rebus.execute(actionHead, arg1,arg2,…)的第一个参数是action头,其它参数直接跟在action头后面。Action头中包含两个信息:要做什么?从哪里来?

“从哪里来”这个参数很重要,因为它给咱们开发、调试提供了极大的便利。试想下,在Action路由表中,咱们能够很清晰地看出一个Action将会到哪个Service处理,但没法直观地看出一个Action是从哪里触发的,而且同样的Action可能由不同的组件触发,这是没法从Action路由表中直观看出来的。

所以,咱们给Rebus增加了一个调试功能,只要打开这个功能,便可以打印Action信息。

另外,如果一个Action被触发,却没在路由表中找到这个Action的路由,Rebus会通过打印错误信息的方式提醒开发者。

自从Action有了源信息,领导再也不用担心我找不到代码的出处了,欧耶!

5 Rebus中的Action路由表

Action路由表这个概念在Flux与Redux中没有,但也很好理解,就是一个很直观的路由配置信息表。它是在Web应用开始初始化时,加载进来的。

在这张Action路由表中,你可以直观地添加、修改、跟踪一个Action会被哪个Service处理。当你希望某个Action被另一个Service处理时,直接在这个Action路由表中进行修改便是。

另外,在这个Action路由表中,咱们可以通过and()让一个Action触发多个service,如上图的第29行。咱们写了一个日志服务TodoLog.logAddTodo,希望系统处理ADD_TODO的同时也记录这个事件。咱们就可以通过and()函数将这个服务绑定到ADD_TODO这条路由后面,and()的参数是一个数据,意思可以绑定多个服务。

但是,必须提醒的是,不建议and()中的服务也修改State,除非你肯定and()中的服务修改的State与Rebus.connet()中的服务修改的State的监听者没有任何交集。所以,再三提醒and()中只绑定跟State无关的服务,比如一些日志服务、系统统计服务等。

可能你会问,一个Web应用就一张Action路由表吗?是的,也许在后续的版本中咱们可以支持多个Action路由表。但一张路由表也有它的好处——唯一性。比如你设置了某个Action的路由,结果另一个同事在另一张路由表中也设置了同名的Action路由,一开始独立开发时可能没有问题,一旦整合在一起,问题就出现了。所以,只有一张路由表是有好处的,大点没关系。

6 Rebus中的组件

咱们都知道ReactJs的一大特征就是支持JSX语法,这使得JS代码中可以直接写“类标签代码”,而且一个组件能够被嵌套在另一个组件中,并接受从上级组件传递进来的参数。

这种一层一层嵌套的写法虽然很直观,但也很蛋疼。就拿上面Redux实现的Header组件添加新Todo这个操作,执行的是传递进来的回调函数addTodo(…)。

这么做有几个问题:

1)写代码时,到底是先约定Header组件要执行的回调函数叫addTodo,写上级组件时按约定传递叫addTodo的参数?还是先写好上级组件,根据上级组件传递的参数名来执行回调函数?到底是先有蛋还是先有鸡?

2)果上级组件传参时传错了,或者子组件写回调函数时名称写错了,如何跟踪代码,只知道光从代码上看,我TM怎么知道这个回调函数是从哪个组件传进来的?虽然现在有些工具能够直接在浏览器上查看组件之间的嵌套关系,但那也是在应用能够正常跑起来的情况才能Debug。

3)组件与组件之间的关系是通过硬编码实现,如果现在有个子组件需要替换,可是这个子组件被嵌入在多个组件中,试问这得怎么找?

组件嵌套是ReactJs的一大亮点,但也是很多人认为ReactJs不适合做大型项目的原因。但我觉得这并不是ReactJs的问题,我们完全可以其他途径解决上面这些问题。比如咱们的Rebus,组件与组件之间不会直接嵌套,而是跟调用后台Service一样,通过Rebus.execute()方法,发起一个Action。比如TodoApp这个上层组件,它嵌套了TodoHead/TodoBody/TodoFoot这三个子组件,但你会发现TodoApp组件是通过execute了三个分别叫GET_TODOHEAD、GET_TODOBODY、GET_TODOFOOT的Action来引入三个子组件,具体引入是怎么的组件,它并不关心。

Rebus总线根据Action路由表(rebus.route.js),分别找到这三个Action对应实现者(在这里咱们通过一个“组件工厂”CompFactory来响应这些Action)。当我们需要替换组件时,只需要在Action路由表中做出修改便是。

换句话说,在Rebus总线面前,每个组件都是平等的。组件只会跟Rebus总线沟通,不会直接嵌入其它组件,也不会被嵌到其它组件中。“组件树”这个概念在Rebus是通过Action消息来实现的,是一种“动态嵌套”关系。

7 Rebus中的State

在Flux/Redux中,应用的各种状态以一棵“状态树”的形式都是从根组件上灌进去,所有子组件的状态一律从这个根组件上继承下来(不管组件树的结构有多深)。这样做的好处就是一旦某个状态发生变化,React组件自动从上到下进行更新。

但是,这么做真的好吗?并不是说一个应用就一棵状态树这个想法不好,我也赞同这种设计,因为状态是Web应用中最重要但又非常容易混乱的信息,“唯一性”对状态来说,非常重要。

可是如果所有子组件的状态都是从根组件一层一层传递进来的话,至少会有两个问题:

组件之间的耦合性高,难以并行开发:子组件的状态是由父组件决定。那到底先写父组件还是先写子组件?

状态变化后,难以跟踪变化的组件:假设你的某个操作修改了某个状态,但这个状态的变化会导致哪些组件更新了?光从Store中是看不出的,也无法跟踪,只能从根组件一层一层往下查,看看这个State被传递到哪个组件中。

在Rebus中,咱们同样维系着一棵“状态树”,并在应用初始化的时就加载进来的。

但不同的是,组件的状态不是从上级组件中传递进来,是通过Rebus获得的,而且组件有权决定自己关心哪个State的变化。

这样做有几个好处:

方便并行开发:因为组件之间没有太过的耦合性。状态都是通过Rebus获得的,大部分情况下都是直接返回状态树中的某个状态,这样的“浅处理”非常适用于复杂系统开发中。

方便单元测试:由于组件直接与状态绑定(监听),要对一个组件进行单元测试,直接修改这个组件绑定的状态便是,即是没有上级组件的存在,也不影响测试。

方便维护代码: 从上面的代码中可以清晰地看出某个组件监听哪些状态,但反过来,某个状态被哪些组件监听了?从组件的代码中是没法直观看出来的。这个问题也不应该通过查阅代码的形式来解决,而应该通过咱们的Rebus来解决。咱们可以给Rebus增加一个方法,打印每一个State的监听者。如下图:

现在咱们既可以清晰地看出一个组件监听了哪些状态,也能看出一个状态被哪些组件监听。这为代码的调试与维护提供极大的方便。

另外,我们可以轻松地打印出某个时刻的状态树或具体某个状态的值。

8 总结

先给有耐心看到这里的人鼓个掌……然后也给我自己鼓个掌……因为对于一个拖延症极度病患者来说,用业余时间写这么一篇技术贴真心不容易。当我写这句话的时候,距离这个帖子的第一句话,整整隔了一个月!——大哥,你是一禅指敲键盘的吗?

言归正传,总结下咱们这个Rebus框架的特点:

实现了单向数据流模式,逻辑层次结构浅,思路清晰。

React组件职责单一,只负责渲染与响应交互。

以路由表的形式控制Action数据的流向,直观、易维护。

React组件之间通过消息的形式实现动态嵌套。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78606.html

相关文章

  • 面试爱奇艺,竟然挂在第5轮……

    摘要:二面休息过后,就来了第二位面试官,面我运维的,运开嘛,如果没有运维知识肯定是不行的。后来的对话中,面试官也表示,可能之前做的更多的是的工作,对于容器这块不熟悉关系也不是很大。整个三面大概也持续了要有不到一个小时。 今天给大家分享我曾经在爱奇艺的面试,过程还是比较有意思的,可以给大家一些参考 聊骚阶段 嗲妹妹:你好,我是爱奇艺的HR,我们正在招聘运维开发岗位,请问您最近有在看工作机会吗...

    Magicer 评论0 收藏0
  • React 328道最全面试题(持续更新)

    摘要:希望大家在这浮夸的前端圈里,保持冷静,坚持每天花分钟来学习与思考。 今天的React题没有太多的故事…… 半个月前出了248个Vue的知识点,受到很多朋友的关注,都强烈要求再出多些React相前的面试题,受到大家的邀请,我又找了20多个React的使用者,他们给出了328道React的面试题,由我整理好发给大家,同时发布在了前端面试每日3+1的React专题,希望对大家有所帮助,同时大...

    kumfo 评论0 收藏0
  • 和小姐姐面试python,是种什么体验?

    摘要:后来的对话中,面试官也表示,可能之前做的更多的是的工作,对于容器这块不熟悉关系也不是很大。 showImg(https://segmentfault.com/img/remote/1460000018525265?w=1718&h=808); 这次给大家讲讲我2年前去爱奇艺面试高级运维开发岗位的经历,希望对大家带来一些帮助。 公众号「Python专栏」后台回复:自动化运维平台,获取整套...

    novo 评论0 收藏0
  • React为什么需Flux-like的库

    摘要:的关键构成梳理了一下,需要配合的库去使用,是因为要解决通信问题。还有各个事件之间,有可能存在依赖关系,事件后,也触发。相比于传统的事件系统,融入了不少的思想。中,将会是最大的门槛之一。 从学习React到现在的一点感受 我觉得应该有不少同学和我一样,上来学React,觉得甚是惊艳,看着看着,发现facebook 安利了一个flux,图画的巨复杂,然后各种例子都有用这个东西,没办法,硬着...

    wangtdgoodluck 评论0 收藏0
  • 深入redux技术栈

    摘要:另外,内置的函数在经过一系列校验后,触发,之后被更改,之后依次调用监听,完成整个状态树的更新。总而言之,遵守这套规范并不是强制性的,但是项目一旦稍微复杂一些,这样做的好处就可以充分彰显出来。 这一篇是接上一篇react进阶漫谈的第二篇,这一篇主要分析redux的思想和应用,同样参考了网络上的大量资料,但代码同样都是自己尝试实践所得,在这里分享出来,仅供一起学习(上一篇地址:个人博客/s...

    imingyu 评论0 收藏0

发表评论

0条评论

李世赞

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<