本系列博文从ShadowWidget作者的角度讲解框架的设计要点。本文解释了ShadowWidget如何在MVC、MVVM和Flux框架之间进行选择。1、ReactFlux框架Facebook官方给React提出了一个flux框架,也自己实现了一个flux.js。虽然这个库设计的很烂,但所有第三方为React开发的单向数据流解决方案都是从这个库开始的下面是Flux官方概念的经典结构图:Action可以简单理解为一条指令(或命令),它包括命令字类型和命令参数数据(或有效载荷)。Dispatcher是一个分发器,Store是一个数据和逻辑处理器,Store会在Dispatcher中为每个命令字注册一个响应回调函数。View是ReactComponent,View经常使用Store中的数据,订阅Store中的变化来刷新自己的展示。数据在几个组件之间的单向流转如下:Action->Dispatcher->Store->View单向流的原理比较简单,大致是这样的,Dispatch中Store注册的回调函数是由Action,Dispatcher解析commandword,找到对应的回调函数实现调用。当Dispatcher如下触发回调时,回调函数具有事件的特征。setTimeout(function(){callback();},0);如果立即调用回调,则它只是一个回调。如果延迟为0秒,回调会在下一个周期被调用,成为一个事件,从而保证数据的单向流动。当然,上面的介绍很简短,把核心机制解释清楚了。Reflux和redux也是利用这种机制来注册回调变化事件。当然,基于事件的回调的处理过程可能非常复杂。例如,Dispatcher也提供了waitFor()的接口来等待一个或多个Action,我们就不详细介绍了。2、React中的MVCReact实现的虚拟DOM部分(即核心库react.js和react-dom.js)是MVC中的“V”。MVC框架图如下:当你只使用React的核心库,不使用reflux、redux等单向数据流机制时,使用的MVC如上图所示。如何构造Controller和Model是自由的,即使你想把它改造成MVVM,你也是自由的。毕竟React的核心库只是提供虚拟DOM映射,和HTML的原生DOM一起提供“View”。后面我们真的要介绍怎么改成MVVM。Flux、MVC、MVVM是等价的架构,我们不能直接把Flux框架应用到MVC上。3、复杂环境对MVC框架的影响。在React中使用MVC的主要缺点是:当应用规模变大时,M、V、C之间的依赖关系会变得复杂。下图并不太复杂,只用到了2个Module。React虚拟DOM对真实DOM进行了抽象,加入了props、state等概念,再加上异步时序干扰,一开始勉强能玩的MVC变得非常难用,开发、调试、定位问题都变成了难的。Flux的引入可以针对性的缓解上述难点。首先,每个View都串联成单一的数据流,这样与Model交互的View(也叫ControllerView)承担了设计的复杂性,其他View只做简单的工作,比如显示界面,简单响应鼠标点击。第二,用Action和Dispatcher来简化Controller,不要用那么多Controller,总结为一个Dispatcher。第三,使用FunctionalReactiveProgramming方法构建响应式单向数据流机制来处理异步时序问题。React生态链中有很多种Flux的实现。它们本质上是一样的,表面上的差别不是太大,通常可以三言两语概括。Reflux采用多店方案,简化了用于集中分发的Dispatcher。Redux采用单store的方案,将Actions分发到很多Reducer函数后进行处理分解。Store+众多Reducer函数”替换。4.ShadowWidget和Redux分两个方向。Redux最大的优点是实现了完全的函数式编程,最大的缺点也是完全的函数式编程。它本身并没有简化设计复杂度,只是将复杂度进行了转移,但是按照官方原生的Flux概念,我们理解每个Storebyobject。设计的时候,在处理Store和View,Action的关系的时候,都是从对象的角度去思考,现在把复杂度转移到了很多reducer函数上。函数式思维不利于设计分解(相对于面向对象的思维)。Redux之所以能够盛行,与React自身的局限性有关。React的虚拟DOM树限制了数据的单向(向下)传输,跨节点读取属性极其不便。如果我们将所有服务渲染的状态数据独立地组装在节点外的全局函数(reducer)中会怎样??所有使用过的状态串起来形成一个大的全局变量(也就是单个Store),reducer函数可以随心所欲地读取它。该方案以大幅度的功能改造为代价,突破了React的局限性。ShadowWidget则相反。尽量保持面向对象的思维习惯,整合Store和ViewModel(稍后详述),减轻思维负担;通过构建Widget树,使用this.componentOf()快速检索相关节点,以方便访问属性;重新设计duals双源属性,建立自动识别数据变化,驱动单向数据流的机制。5、ControllerView数据传递我们来研究一下ControllerView与Store的连接以及与下层View的连接关系。把上图部分放大解释一下,如下:当Store中有数据更新时,通知ControllerView更新界面,ControllerView会从Store中更新界面。读取状态数据以更新您自己的状态。自身状态的变化会触发下层View的联动更新,变化的信息会借助props属性传递到各个子层级。为了准备后面的讲解,这里先提一下Store应该具备的功能:提供事件通知功能,当Store中的状态数据发生变化时,通知ControllerView刷新界面。将状态数据暴露给控制器视图有两种设计选择。一种是让事件通知包含状态数据,另一种是让事件通知不携带数据,ControllerView要主动查询Store。结合FRP编程的特点,第二种设计比较好。如果数据连续多次更新,则应将从Store读取的数据合并为一个,取最新的值。什么时候通知ControllerView刷新可能比较复杂,涉及多种条件的组合,比如只有ActionA和ActionB同时发生才能触发事件通知。6.向MVVM的演进让我们从另一个角度来看flux框架。通过Action相当于“发出”,并认为它被削弱了。另外,Dispatcher也可以弱化。与官方的Reactflux相比,reflux的一个重要改进是去掉了Dispatcher,工具的复杂度因此降低了很多。经过这样的弱化和简化,Flux框架就剩下了Store和View。参考MVC框架,Store对应的是MVC中的Model。在某种程度上,Flux的概念与MVC是兼容的。回流的Store模仿了ReactComponent的设计API,进一步降低了学习成本。不幸的是,它是一个多商店结构。一个Store对应一个View(有时对应多个)。Stores比较多的时候,很容易让开发者迷惑,很多属性都是一时设计的。分不清是放在Store里还是放在View里,经常来回切换。这里我不是说多店设计不对,单店有单店问题。而是如何在多个Stores和多个View之间思考定位有点曲折,不像MVVM那样直接。MVVM采用双向绑定,View的变化自动反映到ViewModel。这是一种非常简单易用的方法。MVVM在人性化方面比其他前端框架要好很多,因为开发者在设计一个功能的时候,首先想到的是界面如何Reflect,加一个按钮,或者加一个输入框,然后把按钮或者输入包围起来框,想想要采取什么行动,例如,点击按钮后下一步要做什么。换成Flux的思维方式,需要更多思考Store和View之间如何交互,不要把“界面应该如何呈现”作为思考的原点,因为Action和Dispatch的设计提示你先考虑Store的数据结构。如果让MVVM支持“所见即所得”的视觉设计,其易用性将把Flux推得更远。除了Flux天生的函数式编程倾向,叠加的工具如react-router自然会使用路由指令,Action命令和状态数据是思考的出发点。比如react-router强调如何设置“路由”是功能开发的第一出发点,不像MVVM是把交互界面的设计作为第一出发点。所以说实话,React生态链上的工具比Vue难用多了,这也是React急需ShadowWidget这样的工具的原因。现在我们清楚了引入MVVM的好处,这是非常值得的。关键问题是,它如何与Flux共存?首先Flux中的Store和ControllerView是可以合并的。大胆一点,绝对不会死。以现有的回流设计为例,如果一个ReactComponent节点没有显示在界面上,比如