百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

前端架构101:MVC的不足与Flux的崛起

ccwgpt 2024-09-13 16:17 92 浏览 0 评论

关于作者

@李光毅,知乎专栏「前端技术漫游指南」以及图书《高性能响应式Web开发实战》作者。曾就职于爱奇艺、百度、知乎等互联网公司,目前就职于 ThoughtWorks,从事全栈开发相关工作。

正文从这开始~~

MVC 的不足

事件

在前几篇中,我演示了一个前端 Backbone.js MVC 框架用于解决实际问题的例子。但 MVC 依然存在几个问题

  • 不可预测:当一个事件发生之后,你并不知道会有谁响应这个事件,是单个对象还是多个对象会响应这个事件
  • 级联修改:当一个事件发生之后,A 组件在接收到事件之后在响应的过程中,还可能发出其他的事件触发后续的修改,你并不知道这个事件会在何处结束,会造成什么样的结果。这也和上一条「不可预测」相对应
  • 响应顺序:如果存在多个对象响应同一个事件的话,有时候对响应的顺序是有要求的,某些变更不可以出现在其他的变更之前
  • 有条件响应:对于传播方而言,并非希望所有的时间都一视同仁的广播出去;对于消费方而言,也并不希望一视同仁的响应所有的事件

你可能会认为事件机制存在的问题是否只存在于 Backbone.js 中,那 AngularJS 这个 MVC 框架会不会好一些呢?

首先 AngularJS(AngularJS 代指 1.x 版本,Angular 代指 2 以及之后的版本) 框架中也支持全局的事件机制,比如 $broadcast, $emit 等等。这样的事件机制支持变化从 $rootScope向各个 contoller 的 $scope 广播全局的变化。如果你对 scope 这个概念不熟悉的话,可以把它理解为模块内部的作用域。

双向绑定

AngularJS 更重大的缺陷在于它的双向绑定机制,或者说是双向数据流 (bidirection data flow) 。也就是说 A 可以把变量传递给 B,当 B 修改这个变量之后,A 中对应的变量值也会发生修改。咋听之下似乎是非常方便的机制,例如在表单这个场景中会非常实用,但是它存在一些隐患。我们以下图中的这个场景为例:

  • Parent Controller 把某个变量以双向绑定的机制传递给 Child A Controller
  • 此时用户在界面上对这个变量值进行了修改
  • 因为双向绑定的缘故这个值同步到了 Parent Controller 中
  • 同时 Child B Contoller 和 Parent Controller 也通过双向绑定把值同步到了 Child B 中,此时 Child B 中的值也发生了修改

也就是说,当你修改 Child A 中的一个值时,你会影响到 Child B 中的值。这样的副作用是危险的,除非你对整个系统里用到这个值的地方了如指掌,否则你极有可能影响到你不愿意被你影响到的地方。

如果 Child A 和 Child B 属于不同的开发人员进行开发, 那么 Child B 的开发人员在排查这个问题是会非常困难,因为站在他的视角上而言,他只知道这个值来自于 Parent Controller,但是这个值又被哪些地方消费了,哪些地方修改了他并不知道。在框架机制内不支持这样的追溯。此时你只能保佑关于这个变量有一个 setter 方法,又或者通过 IDE 的查找功能在代码里全局搜索用到这个变量的地方

职责不明确

回忆一下我在第二篇中列举的 Backbone.js 和 AngularJS 实现的例子,无论是 view 文件还是 controller 文件,其它们的职责并不明确,他们同时在负责好几件事情:

  • 管理 view model,例如负责保存和清空用户输入的值
  • 协调用户流程,例如首先将用户输入值清空,然后提交新数据,再刷新数据列表
  • 负责为不同的 dom 元素绑定事件处理函数

不说大道理,和当下的 React 或者 Angular 组件相比,直接后果是这些模块是无法复用的。如果我想重复使用一个 view 的话,我需要保证我的页面模版里有相同的 id 的元素,又必须保证上下文中有相同 model 层提供相同的借口或者广播相同的事件。关于多职责的坏处在上一篇中已经聊过,就不赘述了。

总结

批评不等于否定。事件机制依然是我们许多问题里可选的解决方案之一;Backbone.js 和 AngularJS 放在现在看也依然是优秀的解决框架,但不是最优解而已。

我个人认为问题在于当下我们解决的问题和过去比发生了许多的变化,随着浏览器能力不断增强,前端需要解决的问题也变得越来越复杂,团队规模也逐渐扩大。如果以 React 步入公众视野的 2014 为节点的话(我以)。2014 年以前我们的开发主要集中在类似于 widget / plugin 级别的功能上;而在 2014 年之后应用级别的功能慢慢变得普及起来。

如果你对比 2014 年以后和之后流行或者崛起的那些框架,你就会感受到其中的微妙之处:

  • 2014 年前:jQuery, Bootstrap, RequireJS, Kissy, Handlebars
  • 2014 年后:Redux, Ngrx, Mobx, Akita, Ngxs

前者倾向于碎片化,各司其职的辅助性的功能;后者倾向于应用级别的数据管理

事件机制和双向绑定更适用于小规模的范围内,随着应用级别不断扩大,副作用的带来负面效用会变得越来越明显。

Flux

我把所有与 Flux 相似的框架在这里都称之为 Flux。包括但不限于:Redux,Mobx,Ngrx,Akita,React 等等。在我看来它们都拥有和 Flux 相同的特征:

  • 单向数据流
  • 全局状态管理
  • store / selector / service 等概念的抽象

在谈论 Flux 之前我们先给 Flux 定一个性:Flux 是成功的吗?

当然是,如今不计其数的网站也应用在使用 React 和 Flux;并且就像我上面提到的,即使是六年以后,在它之后的框架绝大部分是它的追随者而非颠覆者,都能找到 Flux 的影子。但在它诞生之初,无论是在 Reddit, Youtube,还是 InfoQ 上甚至至今为止都有批评的声音,

但在你的那些使用了 Flux 的项目中,有多少项目在可维护性上是成功的?如何定义可维护性呢,我们用 Uncle Bob 的三个标准来回答这一个问题:

It is hard to change because every change affects too many other parts of the system.(Rigidity)

When you make a change, unexpected parts of the system break. (Fragility)

It is hard to reuse in another application because it cannot be disentangled from the current application. (Immobility) —— The Principles of OOD

我相信答案在各位心中已经呼之欲出了。站在工程师的角度上看项目代码的可维护性并不取决于你使用的框架多么的先进,而是取决于使用框架的人和内部的工程师文化

扯远了,说回 Flux。在这里我不会再聊 Flux 的那些基本入门概念。我们重点说一说 Flux 解决的问题,帮助你更好的理解 Flux

不知道有多少人看过 Flux 走向公众视野的第一个视频 Hacker Way: Rethinking Web App Development at Facebook ,这个视频上透露了很多有助于关于我们理解 Flux 的很多信息。这一节的内容不少复述自该视频

首先要强调的是,Flux 并非是为了颠覆和创新而生,而是为了解决我们所说的非功能性需求。

在 Facebook 公司内部工程师需要保证交付软件的质量,但是高质量意味着需要花费更多的时间;但另一方面公司也希望崇尚 move fast 的核心价值,也就是要用更少的时间交付更多的价值,于是这两者间似乎产生了矛盾,如何用更少的时间交付更高质量的软件。

用视频中的原话说,按照顺序他们想达成的目标是

  • Produce higher (quality) code
  • Higher quality software
  • Better code by default
  • We want to do it in less time

其中有意思的是第三点:「Better code by default」。在我看来这就是我在第一篇中强调的 「Falling Into The Pit of Success」有异曲同工之妙(你也可以说我现在眼里只有锤子看什么都是钉子)——你要让你的开发人员一开始(容易)就写出对的代码。

而在他们的项目中最大的阻碍竟然是 MVC 架构

整个宣讲 Flux 过程中最令人诟病的就是这一张图,在我上面提到的批评声音中,最共同的声音就是它们以一种错误的方式实施了 MVC,所以才导致了他们的应用无法拓展。时候演讲者 Jing Chen 也承认演示中的图片确实投机取巧了。它们真正想表达的是这种双向的数据流架构会产生一定的负面效应。

首先就像我在前几篇中提到的那样,从客户端到后端到前端并没有“标准的 MVC” 一说。即使你只在前端领域内寻找统一的 MVC 概念,你也会发现从 Backbone.js, AngularJS 到 Ember.js 的实现各不相同。

正因为大家对 MVC 的理解各不相同,甚至在同一个框架内也没有推荐的最佳实践,于是你会看到在一个框架内解决一个问题的不同实现。其中有一些方案是存在隐患的,但是在小规模的应用内很难暴露出来。但随着团队的扩充和复用代码的越来越多,代码会变得越来越脆弱,因为不同人看到同一份代码的理解是不同的。上图中的情况是非常有可能发生的,但并非是按照上图一模一样的方式,但后果就是跨职责和意料之外的级联更新。

如果你现在站在开发 React 应用的体验上看 Backbone.js 和 AngularJS 的开发体验,你会感觉框架带来的约束是松散的。以 AngularJS 为例,它赋予了你 controller / view 机制,但至于是在多个 view 之间共享 controller,又或者相对于一个 view 嵌套多层 controller,它完全不做任何的限制。这就极易产生上述后果。在下图中 View C 可以访问和修改多个祖先 controller 中的变量(左侧黄色箭头)同时变量又有可能会被 View B 和 View C 使用(右侧蓝色箭头)。

所以你现在理解了为什么 Flux 会尝试用单向数据流解决这个问题了。我们抽取 store 来保证唯一数据源(single source of truth),所有的业务逻辑也都封装在 store 中,避免了用例和服务层(对应后端 service layer)方法散落在各个 controller 中。注意 store 层工作是不会引起任何的副作用的,在 store 完成上一个 action 的工作之前,不会有其他的 action 再次经过 dispatch 达到 store。同时使用 command 模式来避免事件机制造成的的不可预测性。剩下的具体概念你应该非常熟悉了

现在回过头再看 Flux,它其实是一个非常强约束的框架。假设你需要完成一项工作,比如接住后端传递的用户信息里的新增字段,你会非常明确的知道你需要修改 store, 该 view,而不需要修改 action。到了在 store 中新增字段的这一个环节,无论是你是使用 Redux 还是 Mobx 相信你都能迅速的找到对应的 model / reducer 在哪。我想这就是 期望中的 「Better code by default」 吧

简单聊框架的约束

就像我一开始提到的,目前的框架倾向于为你提供应用级别的整套解决方案。并且极其详细的为你划分模块。最初我们只有 model, controller 和 view;但现在我们有 component, store / reducer, action, dispatch, selector / query, reducer, service, effect, 甚至在有的框架中还有更细化的 entity store, entity query。所以当你现在需要开发一个功能时,你能够很轻易的把你的需求拆解为对应的模块,分别把它们开发、测试完毕之后接入应用即可。

有人认为如此强的职责划分和框架约束扼杀了编程的创造力和乐趣。但我认为工业化的代码产出稳定和高效才是最重要的。

现代的前端技术栈已经变得非常复杂了,「精通」已经成为了一件奢侈的事,更别说让整个团队达到相同的「精通」水平。如果你开发的是 Angular 应用,Angular 本身,或是 Rxjs 又或是 TypeScript 哪一个单拎出来都不好对付,指望着人们自我学习或者培训的方式统一大家的水平更是天方夜谭。目前看来「约束」看起来是最简单也是最靠谱的方式。这种约束体现的不仅是在模块的明码标价设计上,还可以体现在使用 lint,TypeScript 等语法的约束上

相关推荐

知乎热议:如何成为前端架构师,赚百万年薪?

作者|慕课网精英讲师双越最近有一条知乎热议:从一个前端工程师,如何根据目标,制定计划,才能快速进阶成为前端架构师?不久之前我参与了一次直播,讲到了自己对于Web前端架构师的理解。架构师这个角色...

学习笔记-前端开发架构设计(前端架构设计方案)

前端开发的技术选项主要包含以下几点,下面对一些名词概念的解释做了笔记:1、分层架构:把功能相似,抽象级别相近的实现进行分层隔离优势:松散耦合(易维护,易复用,易扩展)常见分层方式:MVC,MVVM2、...

关于后端程序员写前端用什么框架更好?

关于后端程序员写前端用什么框架更好?兄弟们,最近是不是被全栈逼疯了?左边Java岗要求会Vue,右边Node.js岗要懂React.......现在连后端都被迫写前端了,但为什么你学了三件套还是被面试...

Ktor - Kotlin Web 框架(kotlin orm框架)

进一步了解Ktor,一个KotlinWeb框架。Ktor是一个为Kotlin编写和设计的异步Web框架,它利用协程并允许您编写异步代码,而无需自己管理任何线程。这是有关Ktor的更...

Web Components实践:如何搭建一个框架无关的AI组件库

作者:京东科技牛志伟一、让人又爱又恨的WebComponentsWebComponents是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的We...

Web3项目开发框架及性能(web开发的三层架构)

Web3的“框架”通常更像是一个工具集(Toolchain)或开发环境(DevelopmentEnvironment),旨在简化智能合约的开发、测试、部署,以及去中心化应用(DApp)与...

开发者的福音,ElectronEgg: 新一代桌面应用开发框架

今天给大家介绍一个开源项目electron-egg。如果你是一个JS的前端开发人员,以前面对这项任务桌面应用开发在时,可能会感到无从下手,甚至觉得这是一项困难的挑战。ElectronEgg的出现,它能...

桌面GUI开发框架汇总(桌面界面开发)

接触桌面应用开发已有一段时间了,对这个领域的相关技术有了一个大体的了解。今天记录一下以作梳理,同时和大家讨论交流。不管哪个领域范畴,根据不同的角度或标准划分,就有不同的分类。今天我从绘制GUI组件的角...

详解下一代开源混合应用框架Reapp

Reapp是一款使用React来开发混合应用的开源框架,为开发者提供了他们开发所需的一切,其中包括各式模块的集合、UI工具包、引导应用的CLI,以及一个预配置的构建服务器,支持Android、iOS。...

360周鸿祎:AI主流开源框架有几十个安全漏洞,没有攻不破的网络

智东西5月16日消息,在今天举办的第二届世界智能大会上,360董事长兼CEO周鸿祎在《大安全为智能经济保驾护航》演讲中表示,全球网络安全从信息安全时代进入“大安全”时代,网络战已经威胁着国防安全、社会...

理解LangChain 开源框架(开源的框架有哪些)

LangChain是一个强大的框架,可以帮助您轻松地开发基于大型语言模型(LLMs)的人工智能应用。让我们更深入地了解一下。LangChain在官网上的官方定义如下:LangChain是一个用于开发由...

多步推理无需训练性能提升10%!斯坦福开源通用框架OctoTools

编辑:LRS【新智元导读】OctoTools通过标准化工具卡和规划器,帮助LLMs高效完成复杂任务,无需额外训练。在16个任务中表现优异,比其他方法平均准确率高出9.3%,尤其在多步推理和工具使用方面...

腾讯Kuikly跨端框架开源,可使用Kotlin创建安卓、iOS等应用

IT之家4月28日消息,腾讯Kuikly跨端框架现已开源,当前已开源Android、iOS平台能力,其他平台逐步开源中。官方介绍称,Kuikly是基于KotlinMultiPla...

腾讯开源 Kuikly 框架,跨端开发新突破

腾讯跨端框架Kuikly正式开源。根据官方介绍,Kuikly是基于KotlinMultiplatform的UI与逻辑全面跨端综合解决方案,由腾讯大前端领域Oteam(公司级)推出,目...

A.I. EI!开源框架(主流的开源框架)

期刊推荐推荐期刊:《IEEETransactionsonNeuralNetworksandLearningSystems》-刊号:ISSN2162-237X-影响因子:14.3-...

取消回复欢迎 发表评论: