前端技术 | 从MV*到Flux

1,578 阅读5分钟

最近要开始搞网页端钱包,本着干一行爱一行的原则,撸起了前端框架。

项目基于蚂蚁金服的dva框架,实际上是对几个流行的开源框架的整合,技术栈包括:

  • react
  • react-router
  • redux
  • redux-saga
  • dva
  • antd

在开始介绍之前,先说一说MV*。大家一定都听说过MVC,在这之后又衍生出了MVP和MVVM,这些都可以统称为MV*。但是,随着前端代码复杂度的增加,人们发现越来越难以管理程序的状态,模块之间耦合严重,代码难以调试,因此很多人认为“前端MVC已死”。

2014年,facebook提出了一个新的概念:Flux,旨在解决这些问题,其核心思想是“组件化 + 单向数据流”。这个框架很快流行了起来,并且逐渐成为目前的主流前端框架之一。为了更深刻地理解这一变化,我们来逐一比较一下它们之间的异同:

1.MVC

用户首先通过View发起交互,View调用Controller执行业务逻辑,Controller修改Model,然后View通过观察者模式检测到Model的变化(具体表现形式可以是Pub/Sub或者是触发Events),刷新界面显示。

从这里可以看出,主要业务逻辑都在Controller中,Controller会变得很重。MVC比较明显的缺点:

  • View依赖特定的Model,无法组件化
  • View和Controller紧耦合,如果脱离Controller,View难以独立应用(功能太少)

2.MVP

为了克服MVC的上述缺点,MVP应运而生。在MVP中,View和Model是没有直接联系的,所有操作都必须通过Presenter进行中转。View向Presenter发起调用请求,Presenter修改Model,Model修改完成后通知Presenter,Presenter再调用View的相关接口刷新界面。这样,View就不需要监听具体Model的变化了,只需要提供接口给Presenter调用就可以了。MVP具有以下优点:

  • View可以组件化,不需要了解业务逻辑,只需提供接口给Presenter
  • 便于测试:只需要给Presenter mock一个View,实现View的接口即可

3.MVVM

为了进一步解放生产力,把Presenter中调用View的接口同步数据变化的重复工作抽象出来,做成一个binder模块,这就变成了MVVM。开发者只需要指明绑定关系,binder模块会自动完成数据同步,这就是所谓的“双向数据流”,不管哪一端的数据发生变化,都会立即同步到另一端。实际上,Vue.js、Angular这些流行的前端框架都使用了双向数据流设计。

双向数据流极大地简化了开发者的工作,但是诟病也随之而来。由于绑定的随意性,某个View对Model进行的修改有可能会对其他的View造成“连锁反应”,再加上各种异步回调,给代码调试造成了很大的困难,往往难以定位数据到底是被谁修改掉的。用专业一点的术语来讲,代码的“可预测性”非常差。因此,为了提高可预测性,很多人主张回归到“单向数据流”模式,其中的典型代表就是facebook的Flux框架。

4.Flux

其实Flux并不是什么新鲜事物,其背后还是经典的MVC思想,但是实现方式上有所不同。Flux的核心是“组件化+单向数据流“,下面逐一进行介绍。

4.1组件化

在传统的MVC设计中,Model中不仅要存储应用程序数据,还需要存储UI状态。另一方面,Controller中不仅要处理业务逻辑,还需要实现各种事件处理逻辑。如果把这部分内容抽出来,和View组合在一起,就变成了“组件”。这样一来,各个模块都可以各司其职,专注于自己的领域,代码的可读性和复用性都可以得到提高。

在实际编程中,一般把纯界面展示的View实现成一个“无状态组件”,在其上层再包装一个Controller-View(也可以称为Container),专门监听事件并更新数据,然后把数据作为props传递给View。这种编程模式可以最大程度地提高组件的可复用性。

4.2单向数据流

为了提高代码的可预测性,Flux采用单向数据流设计。这里引入了3个新概念:

  • Store:每个程序可以拥有多个Store,存储应用程序状态的不同部分。Store对View是只读的,只有Dispatcher可以通过Store注册的回调函数修改Store的内容
  • Action:当发生交互,需要修改Store内容时,需要发起一个Action,包含对应的type和payload
  • Dispatcher:当接收到Action时,会通过回调函数调用所有Store的,完成数据修改

当Store数据发生变化时,会发送一个事件,View或者Controller-View可以监听这个事件,然后完成界面刷新。整个过程是“单向”的,如果View想要继续修改Store,必须重新发起一个Action。

当然,除了View以外,服务器或者Web API也可以直接发送Action给Dispatcher,这就是为什么图中Dispatcher有两个输入的原因。

更为详细的Flux流程参见下图:

通过以上分析可以发现,所谓单向数据流并不是什么新鲜概念,实际上最最经典的MVC设计中,数据流就是单向的。虽然Flux官方宣称它们不是MVC,但我个人认为其实它实际想说的是MVVM,因为MVVM才是双向数据流。

当然,Flux也不是完美的,在多Store协同管理上存在一定的设计缺陷,这也是后来Redux出现的原因,且听下回分解。

最后,以一张思维导图结束本篇文章: