做者 | Harshal Patil译者 | 王强编辑 | 张之栋、王文婧早期的软件架构模式创建在有限的硬件功能上并尊重这一事实,然而,今天的状况已经变了。计算能力在不断提高,而软件架构印证了这种观点。本文从经典MVC提及,详尽解读了当代前端架构及下一阶段的展望。对于前端工程师,以及想从宏观层面理解现代Web应用程序架构的Web开发人员来讲,均能从中获益。javascript
软件架构的核心思想,就是推断软件系统各个组件之间数据流动的方式。html
软件架构的质量取决于你设法推断这些数据流的难易程度!前端
本文要讲的内容,就是在今天的 Web 应用程序背后探索这些数据流和最终的体系结构。Web 应用已从简单的静态网站(双层结构)发展为复杂的多层次、SPA 和 *** 驱动的 API 优先系统。CMS 系统已发展成无头(Headless)和内容优先的系统。java
近年来,前端社区的面貌突飞猛进。早年流行的是 jQuery 引入的 DOM 注入算法,其很快被基于 MVC 的 Backbone.js 所取代。以后一晚上之间,咱们就身陷于双向和单向数据流架构的丛林之中。咱们的成长足迹在某处中断了。曾几什么时候沉浸在 MVC 的世界是如何忽然进入 React 开创的单向数据流时代的?它们之间有什么联系?随着本文的展开,咱们将尝试解开这个难题。node
虽然本文针对的是前端工程师,但想要从宏观层面理解现代 Web 应用程序架构的 Web 开发人员都能从本文中获益。软件体系背后有着大量活动——流程、需求收集、部署拓扑、技术栈等等,但那些已经超出了本文所涉及的范围。react
必要的前置知识——什么是计算机?计算机是一种从用户收集数据 / 信息,并马上或稍后将处理过的数据 / 信息提供给用户的机器。计算机如何收集和展现这些数据呢?它使用软件应用来实现这一目的。web
软件架构的关键是提供合理的手段来组成软件,同时保持一切井井有理。算法
这里的关键在于,软件应用正在处理的数据被称为模型或应用程序状态。一些勇士可能将其称为应用程序的域模型或业务逻辑。应用程序能够是桌面端也能够是 Web 端。编程
本文的宗旨是探索向(Web 前端的)用户表示这种应用程序状态的合理方式,同时让一切井井有理。咱们将探索数据如何从模型流向视图层,仅此而已。redux
经典 MVC——起源将数据与表示分离是(Web 端和桌面端)图形用户界面的核心思想。对于 MVC——模型 - 视图 - 控制器来讲,将表示(视图)与关注域(模型)分离是其主导的设计理念。毫无疑问,MVC 是一项开创性的成果,其影响力绵远流长。
MVC 是为 Smalltalk-80 语言推出的。在 MVC 中,视图(View)对象显示模型(Model)对象持有的数据。在咱们全面研究 MVC 中的数据流以前,咱们必须了解当时(约 20 世纪 70 年代)的软件应用环境:若是要为软件开发写出第一原则,那么它就会是 SoC——关注点分离。并且 MVC 模式多是它第一个真正的落地实践。
这意味着应用软件很是接近底层硬件。
以上这些限制对 MVC 影响很大。因而须要由控制器(Controller)对象负责响应键盘或鼠标等用户输入并转换为模型上的操做。此外,操做系统缺乏 GUI 小部件意味着视图与屏幕内容没法对应。
相反,视图和控制器以配对的形式结合在一块儿。其中视图部分向用户显示输出内容,控制器部分接收来自用户的输入。应该注意的是,屏幕上的每一个控件都有一个视图——控制器配对,这也是小部件(widget)理念的雏形。
今天,在 React、Vue 或 Angular 中,这种视图——控制器配对与组件的思想是同样的,尽管状态处理层面的具体机制有所不一样。
关于 MVC 就介绍到这里,下面的图片展现了 MVC 中的数据流示例。在这个例子中,咱们有一个带递增和递减按钮的简单计数器。计数器状态维持着模型。此外,咱们把两个按钮简化成了一个来简化叙述。
经典 MVC
在协做方面:模型实现了观察者(Observer)模式,被一个或多个视图对象注册(subscribe)。当模型更改时会触发事件,事件响应后视图也会更新。
MVC 中有两条数据流。在视图流中不涉及模型,只涉及 UI 的更改。显示按钮单击效果或对鼠标滚动事件做出响应就是视图流的例子。
MVC中的数据周期
人们很快就意识到应用程序状态没法与 GUI 彻底隔离。咱们老是要维护某种表示逻辑或视图状态。
复杂的图形界面须要额外的状态,这些状态只用来辅助 UI 小部件,实现更好的用户体验。
但这可能会致使一些问题。来看看以前的计数器示例。当计数器达到 10 时,咱们必须将标签的颜色从黑色更改成红色以表示警告。这种颜色变化行为实际上不是业务逻辑或关注点。这是纯粹的美学部分(用户体验),须要加进来。真正的问题是——放在哪里?是模型仍是视图?
因为这种表示逻辑或视图状态基本上是从域模型派生的状态,所以必须在模型对象中维护它。可是域模型又要维护视觉部分(好比说红色),这种定义就很尴尬。若是咱们将它放在视图对象中,那么它会引入另外一组问题。咱们的标签小部件再也不是通用的了,没法在其余地方复用。此外,在视图对象中放一个带有硬编码数字 10 的条件,意味着咱们正在泄漏某些业务逻辑。
为了解决这个问题,咱们在原始的 MVC 中添加了另外一个实体——应用程序模型(Application Model,AM)。有了 AM 之后,视图——控制器配对再也不直接访问模型了。相反,它们向 AM 事件注册并使用它来访问所需的数据。
应用程序模型MVC
数据流和经典 MVC 是同样的。固然,每种模式都有其优势和缺点,AM-MVC 也不例外。最突出的问题是AM没有对视图对象的直接引用,所以即便 AM 被设计为维持视图状态,也没法直接操做后者。
总的来讲,引入应用程序模型会使视图特定的状态远离域层,并下降了视图对象的复杂性来简化视图对象。这和表示模型(Presentation Model)很像,这是 Martin Fowler 在其开创性研究中创造的概念:
现代桌面架构的时代表示模型的本质是一个彻底自包含的类,它表示 UI 窗口的全部数据和行为,但没有任何用于在屏幕上渲染该 UI 的控件。视图只是将表示模型的状态显示在屏幕上。
时光飞逝,世界也在不断变化,新一代操做系统开始展示威力。应用程序远离了底层硬件,它们之间加入了完整的内核、OS 驱动程序和实用程序。像 Windows 这样基于 GUI 的操做系统提供了开箱即用的 UI 小部件。
再也不须要控制器来监听输入设备了。视图对象的理念改变了。
控制器的大多数功能都由操做系统接手。视图的理念发生了变化:以前它只是一个小部件,如今它是一个众多小部件的组合;一个视图能够包含另外一个视图。视图在某种意义上变成了双向的,它响应用户操做并显示模型数据。
前端世界中视图的理念与这个概念很是类似。在 Web 环境中,视图是一个完整的页面。
经典 MVC 变得过期且难用。为了适应这些不断变化的环境,Dolphin 团队在 1995 年正在寻找一种建立用户界面的新模型。Dolphin 团队如何找出新设计的历史可参阅如下记录,本文再也不赘述。http://aspiringcraftsman.com/2007/08/25/interactive-application-architecture/
在协做方面:总而言之,该团队最终将 MVC 模型旋转了 60 度,他们称其为 Twisting the triad。因而咱们有了 MVP。
根据实现,视图注册到模型上,并依赖表示器处理复杂逻辑;或者在其余状况下,视图只依赖表示器处理一切。
正如 Martin Fowler 在关于 GUI 架构的论文中总结的那样,他将 MVP 实现分为监督控制器 MVP 和被动视图 MVP。从图中能够看出它们的差别和各自的数据流。
MVP——模型视图表示器
MVVM——模型 - 视图 - 视图模型MVP 很棒,有许多可能的变种和复杂的细节。但从前端应用程序的角度来看,MVVM 确实更胜一筹。在一些实现中,后者也被称为模型 - 视图 - 绑定器。MVVM 很像被动视图 MVP,但增长了数据绑定的功能。它是一种编程技术,未来自提供者和消费者的数据源绑定在一块儿并同步。它摆脱了咱们过去须要用来保持视图和模型同步的许多样板代码。这样咱们就能在高得多的抽象级别上工做了。在协做方面:MVVM 有额外的绑定器(Binder) 实体,负责使视图与视图模型保持同步。每次视图模型上的属性更改时,视图都会自动更新以反映 UI 上的更改。
MVVM——Model View ViewModel
MVVM 中的数据绑定已经成为许多前端库的基础,包括 Knockout、Angular、Vue.js 和 React 等。
咱们将在 Web 应用程序部分再讨论数据绑定。
进入 Web 应用程序领域就像原始的 MVC 模式同样,出现了一种用于 Web 应用程序的模式。它被称为 Web MVC。实际上,Web 应用程序的构建和部署方式让 MVC 在 Web 端比在桌面端更加顺其天然。
社区的主要困惑是不知道桌面 MVC 和 Web MVC 是两种不一样的模式。要是 Web MVC 当初不叫这个名字,你们就会清楚多了。
Web 应用程序是分布式应用程序的子类别。虽然 MVC 对 Web 应用程序感受更天然一些,但通常来讲构建分布式应用程序是很困难的。代码的某些部分在服务器上运行,同时还要保留在客户端(例如浏览器)上。
在 MVC 的语境中讨论 Web 应用程序时有三个不一样的数据周期,所以有三个 MVC 实现:服务端 MVC、浏览器内部的 MVC 和前端 MVC。浏览器负责三种类型交互的中介:大规模 Web 应用程序架构的重点在于肯定代码的哪一个部分应该在哪里执行。咱们有服务端驱动的应用程序或富客户端驱动的应用程序。在二者之间,咱们能够无限自由组合。
在用户和客户端代码之间。
浏览器有本身的模型、视图和控制器。做为开发人员,咱们没必要担忧浏览器 MVC。
服务端 MVC,亦即 2 号模型服务端 MVC 的第一个众所周知的实现是 Sun Microsystems 针对 Java Web 应用程序的 2 号模型(Model 2)。
服务端 MVC——前端视角
这个 MVC 与传统的 MVC 很是类似,但因为数据跨越客户端和服务端边界时数据流周期时间呈指数级上升,所以前者多出来不少复杂性。须要注意的一些事项有:前端控制器(Front Controller):是一般由底层技术栈提供的组件,用于处理 HTTP 请求调度。例如 Java Web 应用程序中的 Servlet 容器、ASP.NET 中的 IHttpHandler 或 Node.js 中的 HTTP.Server 类:
https://nodejs.org/api/http.html#http_class_http_server
今天的 ***——服务端渲染则是彻底不一样的概念。然而事实并不是如此。因为整个 HTML/ 内容是由服务端生成的,而且不涉及客户端 JavaScript 代码,所以彻底使用服务端 MVC 构建的 Web 应用程序仍被视为 ***。
超越服务端从这里开始就有意思了。不知不觉中,几乎全部浏览器都开始提供 JavaScript 引擎。在我看来正是 AJAX 改变了 Web 应用程序。谷歌是最先的实践者,在其邮件客户端和地图应用中采用了这项技术。
这个世界由服务端 MVC 生成 HTML+JS。JS 代码遍及全部页面。JavaScript 主要用来减小服务端视图周期以改善用户体验。表单提交、输入验证等内容由客户端代码处理。
它是 Web 应用程序历史上最流行的架构。大多数 B2C 应用程序、SEO 友好网站,尤为是使用 CMS(内容管理系统)构建的网站都在使用它。客户端代码量取决于应用程序的需求。
这样的架构从未真正标准化,所以没有名称。它一直在渐进发展,仍被认为是 Web MVC 的扩展。ASP.NET MVC、Java Struts、Python Django、Ruby ROR 和 PHP CodeIgniter 是流行框架的一些例子,它们大量使用服务端 MVC 或 Web MVC。
固然,这种标准模式还有不少变体,但它们对当代前端架构没有任何实际影响,能够忽略。
基础 RIA——富互联网应用架构了解过这些背景,咱们如今准备讨论当代前端架构。当代前端架构主要解决的是构建 RIA——富互联网应用程序的问题。RIA 的确切定义并不存在,由于不少事物都能用它描述。但总的来讲,RIA 或富 Web 应用是应用程序的一个类别,其中应用程序严重依赖客户端代码,而且它们的用户体验很是接近桌面应用程序。它主要使用支持 SPA(单页面应用程序)的框架构建。来自 Web MVC 的 服务端视图周期的数据流这里不存在。它一般只有一个初始 HTML 页面,而后使用客户端代码和路由解决方案来渲染后续页面。
构建 RIA 是一项复杂的操做,这种操做从之前基于桌面的 GUI 架构学习发展而来。视图模型、观察者、组件等就是从这些架构中借用的一些理念。Oliver steel 在他 15 年前的博客文章中(相信我,它是最出色的文章之一),为理解 RIA 数据流提供了很好的参考架构: https://blog.osteele.com/2004/08/web-mvc/
客户端变重了——SPA 的时代
RIA 参考架构和 Web MVC 之间最显著的区别是前者的顶层架构中缺乏控制器和视图。然而字面意义上讲它们并无消失。若是咱们看一下底层,控制器和视图仍然存在,只是承担的角色大大缩减了。后端主要是 API 驱动的。视图仅限于生成 JSON,控制器负责编排传入请求并将其映射到适当的业务工做流。
GUI 模式很难?若是你已经深刻探索了前面的模式,那么你将意识到 GUI 模式与其余软件工程模式有所不一样。以可复用的面向对象软件的元素为例:大多数模式都独立于技术或语言,但 GUI 模式并不是如此。这些模式适用于人机交互的边界。用户和反作用本质上是模式的一部分。注意:可复用的面向对象软件的元素已经指出了经典 MVC 的本质。
GUI 模式适用于 HCI——人机交互的边界。用户和反作用本质上是模式的一部分。
所以,在不考虑基础框架或语言的状况下几乎不可能在理论上讨论它们。到目前为止,咱们能够在至关高的抽象级别上探索这些模式。但当咱们接近本文的核心时,咱们将依靠不一样的库或框架来描述这些模式。
大多数 Web UI 模式能够分为三个阶段,即进化、变革和当代阶段。
前端图谱——宏观视角
进化模式只是服务端 MVC 的扩展。它们并不会试图改变方向或发明全新的事物。它们会一步一步地改进,以补足现有应用程序的缺陷。而变革模式是将前端应用开发与服务端驱动的工做流程分离的那些理念。它们的到来标志着 SPA 应用的兴起。当代模式就是对这些变革模式的二次修正,也是前端社区前进的大方向。
DOM 注入算法由 jQuery 引入和掌握的这项技术是编写大规模客户端应用程序的真正起步,尽管 jQuery 并未真正解决架构问题。它旨在简化当浏览器中存在太多不一致时的 DOM 操做。它提供了与浏览器无关的 API。
虽然我不以为这是有意为之,但 jQuery 把 DOM API 简化到了很难将其与正常的语言 API 区分的程度。这反过来又让开发人员将 DOM API(视图层)与业务逻辑(模型)完美地混合在一块儿。
须要指出的一点是,它仍然在同一服务端 MVC 的上下文中。这只是一个扩展。没有真正的控制反转。视图 / 页面的整体控制仍然由服务端代码驱动。
jQuery——HTML 代码
jQuery——DOM 注入算法
在上面的代码片断中,模型、视图和表示器 / 控制器被混合成一个单体代码结构。当模型只包含一个属性时就是这种状况。想象一下,尝试构建一个没有服务端视图周期的 Web 应用程序(好比 SPA),作这样的事情是没有意义的。与 DOM 交互的代码简洁地注入了其余应用程序逻辑,这就是为何它被称为 DOM 注入算法(注意:我不肯定 DOM 注入这个术语是否是什么规范名称)
Backbone.js——MV*正如咱们在 jQuery 中看到的那样,在开发应用程序时显然缺乏构建和组织代码的方法。因而 Backbone.js 应运而生,成为了新的进化解决方案。它是首批将 MVC 风格带给客户端的库之一。
Backbone.js 数据流
咱们看一看 Backbone 数据流示意图,就能够清楚地看到模型和视图,可是没有控制器的等效对象。模式在不断发展,客户端 MVC 只是以前 MVC 架构的进化。在这个进化过程当中,大多数 JavaScript 社区都赞成模型和视图的定义,但针对控制器没有造成共识。考虑到客户端环境 ,控制器的理念并不合适。控制器留待进一步解释。
至于 Backbone 而言,控制器不存在。那么它适合哪里?是 MVC、MVP 仍是 MVVM?借用服务端 MVC 的定义,控制器有两个职责:以传入的 HTTP 请求的形式响应用户操做,并协调模型以生成视图(HTML 页面)。在 Backbone 的状况下,视图和路由器共享这些职责,可是缺乏独立的控制器或表示器的概念。
一些开发人员认为 Backbone 是 MVP,其中视图相似于表示器,模板(Template)是视图,而 Backbone模型和集合(Collection)构成模型。
正如 Addy Osmani 在他的博客中所说,最好不要强行把 Backbone 归类到任何特定的设计模式。设计模式应被视为应用程序结构的灵活指南,由此看来 Backbone 既不像 MVC 也不像 MVP。相反,它借鉴了多种架构模式中的一些最佳概念,并建立了一个运行良好的灵活框架。
这就是 MV* 或 模型 - 视图 - 其余 的诞生历程。强烈推荐 Addy Osmani 的博客文章:理解 MVC 和 MVP——适合 JavaScript 和 Backbone 开发人员。https://addyosmani.com/blog/understanding-mvc-and-mvp-for-javascript-and-backbone-developers/?source=post_page-----fb5b500b0231----------------------
与以前的 jQuery 相比,Backbone 能够生成更加结构化的代码。
Backbone.js 中的视图模板
在 Backbone.js 中建立一个模型
在 Backbone.js 中建立一个视图实例
在 Backbone.js 中连接视图和模型
前文中我将 Backbone 称为下一个进化解决方案。缘由是它只是补充并扩展了服务端 MVC。例如,若是咱们的后端是 RESTful,意味着前端代码只是用来表示服务端模型的手段,那么 Backbone 已预置为与 API 同步:
Backbone.js 中自动生成的集合方法
并且 Backbone 中还有许多其余小型约定,感受只是扩展而已。总之,Backbone 可能不是当时惟一的解决方案,但它在代码结构和组织领域确实作出了开创性的工做。像 jQuery 同样,它被许多产品采用。
Knockout.js——前端的数据绑定 Knockout.js 是咱们基本模式的最后一个案例。它旨在为 JavaScript 实现 MVVM——模型 - 视图 - 视图模型。它也确实作到了。Backbone 解决的是代码组织和结构的问题,而 Knockout 是在 声明式数据绑定的帮助下有效地实现 视图层。声明式绑定的优势与其余声明式编程结构相同:可观察:Knockout 在事件之上提供更高级别的抽象。这容许 Knockout 自动跟踪视图模型props 之间的依赖项。若是须要,咱们能够注册可观察属性。
Knockout.js 视图——双向绑定
Knockout.js 中的视图模型——使用计算属性响应
虽然 Knockout 为视图和视图模型提供了定义良好的结构,但却没关注应用程序模型的问题。这使得 Knockout 高度专一、能够普遍适应,由于它能够用做库而非框架。我见过有人用它构建迷你 SPA 应用程序,其中 Web 应用程序有多个页面,每一个页面都是一个小型的 Knockout 应用。这个StackOverflow 答案清楚地定义了 Knockout 的 MVVM 实现的范围: https://stackoverflow.com/a/14516490/5723098
一般假设 Knockout 模型驻留在服务端。视图模型只是使用 Ajax 或等效方式询问服务端模型。
它在 DOM 更新用途上取代了 jQuery 和像 Handlebars 这样的模板解决方案,但仍然使用 jQuery 来制做动画、Ajax 等实用程序。与 Backbone 结合后,它能够做为 MVVM 模式的完整实现。理论上讲这可能已成事实,但在此以前,这些概念已经被下一代工具链吸纳过去了。
接下来 Angular 一、Aurelia、Ember.js 等表明的变革旅程开始了。
因为它与.NET 世界紧密相关,所以被普遍用于 ASP.NET MVC 应用程序中。像 Backbone 同样,它是另外一种进化解决方案,针对的是稍微不同的问题。并且客户端代码依旧只是服务端 MVC 模式的扩展。服务端仍然是主导架构。彼时 Backbone 和 Knockout 常被拿来对比,但实际上它们是免费的解决方案。
Knockout 和 Backbone 都是 JavaScript 库。不知何故,Backbone 被视为一个框架。为何?这里没有肯定的答案,但多是由于视角不一样。因为强调代码结构,Backbone 始终采用更高级别的抽象处理。此外,Backbone 从没想过要取代无处不在的 jQuery(即便在 2019 年,最流行的 100 万个网站中有七成都在使用 jQuery),而 Knockout 与核心 jQuery 功能高度重合(好比 DOM 操做),这天然让 Knockout 举步维艰。所以与 Backbone 相比,Knockout 的应用被大大局限了。但它仍然是前端社区首批实现的声明式数据绑定之一,值得在这里专门提到它。
Angular 1——给我控制权jQuery 对 DOM 意味着什么,Angular 1 就对通常意义的前端生态系统有着一样的影响。它永久改变了构建大规模客户端应用程序的概念。做为一个框架,它引入了许多概念——模块系统、依赖注入、控制反转和更简单的数据绑定等。
曾几什么时候,选择正确的 JavaScript 库,并为前端构建完美的技术栈仍然是一件痛苦的事。Angular 1 提供了一种更简单但有凝聚力的替代方案。Ember.js 和其余相似的框架也能够这么说,可是 Angular 1 的普及度是彻底不一样的层面,称得上是那个时代的选择。
是框架仍是库?先前的解决方案更像是库而不是框架。毫无疑问,Angular 1 是一个明肯定义的框架。框架和库之间的关键区别是 IOC——控制反转。做为框架,Angular 1 具有:从某种意义上说 Angular 1 是变革解决方案,它再也不是对服务端 MVC 的简单扩展,而是在页面上洒满了客户端代码。Angular 1 使 SPA 成为构建下一代用户体验的几乎是事实上的一流解决方案。
模块系统:Angular 1 引入了特定于框架的模块系统。模块是几乎全部语言的代码组织的基础。JavaScript 直到 2015 年才有模块系统(浏览器直到 2018 年才支持它)。Angular 在组织方面领先时代。
使人困惑的术语:Angular 1 使用的一些术语显然使人困惑。例如,Angular 1 用 $scope 做为视图模型,但它也有控制器能够扩充 $scope 对象。可能正确的名称能够是 VMFactory 之类。此外,Angular 1 有三种服务配方,其中一种名为 Service。
还有许多其余小问题。另外,Angular 2 或简称 Angular 是一个完全的更新,它就像一个全新的框架。除了名字和少许概念以外,两代版本之间找不到什么共同点。
Angular.js——概览
多年来,Angular 1 发布了一些小版本更新,修复了许多复杂的小细节。最重要的是增长了组件模型,组件模型是前端世界大多数理念的交汇点。
Angular 1 在前端社区的遗产影响深远。它的优缺点帮助社区了解了软件架构的重要性,并为编写可扩展应用程序的工做提供了基准。它的缺点 / 不足成为了将来架构解决问题的基础。
当代前端架构现代应用程序在各个层面都很是接近桌面应用程序。当今环境中的一些显著变化包括:
许多新的框架和工具使用 JavaScript 做为目标语言,而非将其用做源语言。一些不错的例子有 Elm、PureScript 和 ReasonML 等等。
当代前端架构是这些不断变化的需求的反映。顾名思义,它们创建在整个前端社区从过去学到的经验之上。
早期的软件架构模式创建在有限的硬件功能上并尊重这一事实。今天的状况已经变了。计算能力在不断提高,并且软件架构反映了这种观点。
以上假设能够与今天的前端架构相关联。这些架构融合了三大核心原则。
数据流占据舞台中心考虑到前端代码须要运行的领域众多、环境多样,业务逻辑占据了中心位置。做为工程师,咱们花费更多时间来阅读和调试,而非编写代码。咱们必须直观地跟踪代码。每当修复错误或向现有代码库添加新功能时,了解其可能致使的影响和回归是相当重要的。
在大型代码库中作到这一点的惟一方法是确保 咱们了解数据在应用程序中的流动方式。这是软件架构最重要的目标。
今天,任何框架都是围绕这条关键原则构建的。明确区分状态和视图是很是重要的——鉴于简单的基于事件的容器在向更复杂的状态容器(如Redux、Vuex 和 Ngrx 等)转变,这也是显而易见的。因为咱们严重依赖事件或发布/订阅系统,数据(或控制流)将流经应用程序的每一寸角落。对数据流的重视再也不局限在局部环境。相反,做为一种核心思想,如今咱们要在整个应用程序中考虑数据流。
Angular 1 已经证实双向数据流(即时只局限在视图或组件上)能够带来纷繁复杂的控制流。此外,React 已经证实单向数据流很容易推断,所以包括 Angular 2 在内的现代框架也遵循了这一理念。
基于组件的架构转向基于组件的用户界面是数据流第一原则的必然结果。在谈论组件时,有三个方面须要考虑。
首先是数据流的重点:传统架构侧重于水平分工。可是基于数据流的思惟要求垂直分工。在敏捷世界中,这就是咱们设想用户故事的方式。基于 MVC 的架构不容易作到这一点。没有简单的方法能够经过一项功能共享或复用 UI 代码(这里的问题是——当功能在横向组织推进下在整个代码库中传播时,咱们怎样才能真正隔离功能呢!)。可是,封装入一个完整功能的组件能够轻松配置为可共享和可打包的实体。
其次是不断发展的视图状态:过去,视图状态与业务状态相比占比较少。但随着应用程序变得更具交互性,其占比也获得了爆发式增加。视图状态须要接近实际视图。视图状态很复杂,一般表示必需的时间关联数据,如动画和转换等。相似 MVC 的架构没有什么很好的方法来封装这种状态。在这里,做为封装核心单元的组件很是有用。
第三个方面与 UI 开发的原子单元有关。首先咱们提出一个问题:
共享 UI 功能的最佳方式是什么?共享 UI 功能意味着它能够包含如下四个部分:
结构(HTML—视图)、样式(CSS—视图)、行为(JavaScript—视图模型)和业务逻辑(模型)。
因而组件的概念应运而生。咱们意识到组件只是 MVVM 模式 或MV* 模式 的一个很好的实现……
但具体而言,如何表示 UI 组件?我能找到的最接近的概述是 Deric Baily 的博客文章:
https://derickbailey.com/2015/08/26/building-a-component-based-web-ui-with-modern-javascript-frameworks/
在语言级别,一般使用模块或包来表示可复用功能的构建块。JavaScript 也有模块。但这还不够。
组件的这种抽象概念容许框架做者根据他们的须要定义具体的实现。
此外,良好的组件是高度可组合的,可实现分形架构。例如,登陆表单组件能够是标准登陆页面的一部分,或者在会话超时时显示为对话框。一个组件只要定义好接收的属性和发送的事件,就能够在知足基础框架要求的前提下复用在任何地方。
组件能够是由 Webpack 等打包器生成的函数、类、模块或打包代码。
如前所述,组件只是一个 MVVM 模式的良好实现。而且因为组件的可组合性,咱们将 MVVM 模式实现为分形(分形是一种自我重复的无止境模式)。这意味着咱们在多个抽象级别处理多个独立的的 MVVM 控制流。
实现 MVVM 的无限组件分型——循环套着循环!是否是很像《盗梦空间》?
并且,这正是咱们对架构所指望的。
让框架处理 DOMDOM 用起来既昂贵又繁琐。当 状态( 本地或 全局)发生变化时,DOM 能以某种方式自动更新就最好了。此外,它应尽量高效同时不干扰其余元素。在将 DOM 与状态同步时有些事情须要注意。一个好的架构能够实现多级抽象。它容许咱们一次查看一个抽象(细节级别)而没必要担忧其余级别。这是制做可测试和可读解决方案的关键所在。
更新 DOM:在检测到实际更改后,框架须要更新 DOM。许多框架(如 React、Vue 和 Preact 等)使用虚拟 DOM(对时间优化),而 Angular 使用增量 DOM(对内存优化)。
定义当代前端架构早期 GUI 架构的范围主要集中在代码结构和组织上。它主要关注模型与其表示之间的关系,也就是视图。那是当时的需求。
今天状况已经发生了变化。现代前端架构的需求已经远不止简单的代码组织。在社区中,咱们已经把这些知识传承了下来。
大多数现代框架已经在组件的帮助下标准化了代码结构和组织概念。它们是当今架构的基本单元。
还应注意,架构与其实现框架密切相关。这能够追溯到这样一个事实,也就是 GUI 模式很复杂,而且因为它直接对接用户而没法独立于其实现来讨论。咱们能够继续说一个框架就是它的架构。
设计良好的组件系统是任何前端架构或框架的最基本需求。除此以外,它必须解决前面讨论的许多其余问题,如声明式 DOM 抽象、显式数据流、更改检测等。大多数元素都在下图中高亮显示:
现代前端架构元素
考虑到全部这些因素,咱们可能会想要建立一个参考架构,但这根本不可能。每一个框架或库在实现时都有本身的库特性。例如,React 和 Vue 都支持单向数据流,但 React 更喜欢不可变数据,而 Vue 则支持可变性。所以,最好针对单个框架来全面了解其架构。
话虽如此,咱们仍然能够尝试建立近似的参考架构,以下所示:
现代参考架构
到目前为止咱们描述的全部特征或元素都包含在这个架构中。它干净地映射到三层架构,但第二层的中间件是可选的。第二层表示 UI 服务器,理解的人很少。UI 服务器只是一个服务器,旨在为现代重客户端的 UI 应用程序提供服务。它负责正交领域,如 SSO 集成、身份验证和受权,服务端渲染、会话管理、服务 UI 资产和缓存等。此外,当大规模应用程序在某处部署 API 服务器时,它充当反向代理以调用 API 避免 CORS 问题。这里事实上的标准选择是 Node.js,由于许多框架都在 Node 中编写了 *** 渲染器,而 Node 因为其异步特性而擅长处理大 I/O 请求。咱们将在另外一篇关于 Web 应用程序拓扑的文章中进一步讨论 UI 服务器。
当代架构最重要的变化是模型的概念。
模型再也不被视为一个黑盒子。它被分隔成应用程序范围的全局状态和组件范围的本地状态。全局状态一般使用复杂的状态容器(如 Redux、Mobx 和 Vuex 等)管理。每一个组件的本地状态是三个事物的联合——全局状态切片、组件的私有本地状态(异步数据、动画数据和 UI 状态等)和由父组件做为 props 传递的最终状态。咱们能够将本地状态视为模型和视图模型的更好抽象。当咱们将 GraphQL 添加到这个等式中时,状态管理会发生变化。
数据从上到下,从父组件流向子组件时是单向的。虽然框架容许数据直接反方向流动,但不鼓励这样作。相反,事件是从子组件中触发的。父组件能够监听或忽略它们。
不完整的蓝图这个参考架构并无真正捕捉到当代架构的所有本质。大多数 Web 流量由静态网站和 CMS(内容管理系统)驱动。现代工具链已经大大改变了咱们开发和部署这些应用程序的方式。在 CMS 的状况下,他们经过解耦前端与后端而变得 无头(Headless)。Strapi 和 Contentful 等的崛起就是明证。与此同时,咱们再也不使用纯 HTML 和 CSS 构建静态网站。静态站点构建器和生成器也变得很是流行。咱们如今能够使用相同的前端框架来构建由复杂的构建工具辅助的静态网站。使用 React.js时,咱们能够使用 Gatsby.js 和 Vue.js,咱们有 Nuxt.js。当咱们编译代码时,它会生成一个静态网站,能够完整部署到任何静态 Web 服务器。这种技术称为预渲染,与服务端渲染对应。
这里咱们有了另外一个用于构建静态网站的当代架构。这里的思想是使用像 Strapi 同样的无头 CMS,并使用像 Gatsby 这样的静态网站构建器来构建前端。在构建中,当咱们生成静态网站时,咱们从 CMS 中提取全部数据并生成页面。
看成者更改无头 CMS 中的内容时,咱们从新触发咱们的静态网站的构建,使用新内容构建网站并当即部署到服务器或 CDN。
构建静态网站——现代方式
这个新的工做流程就像运行一个成熟的动态网站同样好用,也没有 CMS 的缺点,如复杂的部署和缓慢的加载时间等等...... 因为静态网站能够直接部署到 CDN。咱们得以快速加载并改进缓存。咱们还摆脱了静态网站的全部问题,如更新周期缓慢和缺少可复用性等。这里引述 Nuxt.js 网站的愿景——
咱们能够进一步考虑使用 nuxt generate 并托管在 CDN 上的电子商务 Web 应用程序。每当产品缺货或补充库存时,咱们都会从新生成 Web 应用程序。但若是用户在此期间浏览这个 Web 应用程序,由于有了对电子商务 API 的 API 调用,应用将保持最新状态。无需再用服务器 + 缓存的多组实例!
进一步考虑单向架构的话,能够经过多种方式实现它们。框架有本身的作事方式。一些不错的例子包括:总而言之,现代前端解决方案构建在基于组件的单向架构之上。
BEST、Vuex 和 Ngrx 等。
Andre Staltz 在他的博客文章中很好地描述了这些模式:AndréStaltz——单向用户界面架构:
https://staltz.com/unidirectional-user-interface-architectures.html?source=post_page-----fb5b500b0231----------------------
当代仍是现代???到目前为止,咱们有意不用“现代”这个词,而一直在说的是“当代”。今天的架构实践仅仅是咱们旧有理念的进化。咱们社区试图将新事物融入现有的生态系统,老是留在边界内,不多打破常规。所以,“当代”这个词更能准确地描述这种理念。
在定义“当代”时,咱们必须将全部松散的目标联系起来,必须将过去、如今和将来联系起来。我能够想到三种可能的连接——将来——函数组件
将今天的组件与历史上的 MV* 联系起来?到这里事情应该都很清楚,但可能会出现一个问题,那就是这些模式如何与以前的模式联系起来。组件不是 MVVM 或 MV* 的更好实现吗?
如前所述,对于当代架构而言这只是一个底层的问题。然而,当代模式是关于整个应用的推断。它们处理的是更高级别的抽象。UI 组件是一个原子单元,它从父级接收全局状态切片,将其与本身的本地状态组合,并将输出显示给用户。
单向模式能够解决更大的难题。它说的是在兄弟组件之间通讯并维护应用程序范围的状态。若是组件容许垂直分工,这些模式会为整个应用程序带回水平分工。
Web 组件的场景若是仍是有些糊涂,请考虑 Vue.js 这个例子。Vue 组件是 MVVM 的完美实现,同时咱们能够使用 Vuex(Vue 的单向状态容器)来管理应用程序范围的状态。架构存在于多个抽象层次。
组件架构几乎是全部框架的基础,人们正在尝试将组件的概念标准化为官方 Web 标准,尽管它们背后的推断方式彻底不一样。此外我将它们称为尝试,由于即便成为了标准,许多框架做者也担心其可行性。
在本文中,最重要的关注点是数据流。Tom Dale 很好地总结了这个问题:
根据个人经验,与框架无关的组件还有很长的路要走。
关于其余问题须要看 Rich Harris 的博文《为何我不用 Web 组件》:
https://dev.to/richharris/why-i-don-t-use-web-components-2cia?source=post_page-----fb5b500b0231----------------------
这并非说当咱们定义本身的技术栈时应该彻底避免它们。通常的建议是从按钮、复选框和收音机等枝叶组件开始一步步慢慢来,老是要谨慎行事。
函数组件和 Hooks——这是啥?当咱们将组件看成 MVVM 的实现时,咱们一般指望一个视图模型对象,它的 props 和方法由视图经过绑定使用。在 React、Vue 和 Angular 等状况下,它一般是类实例。可是这些框架还有函数组件(没有任何本地状态的组件)的概念,其中实例根本不存在。此外,React 最近引入了一种使用 Hooks 编写组件的新方法,容许咱们在没有类语法的状况下编写有状态组件。
React Hooks——你注意到这里缺乏“this”指针了吗?
这里的问题是——视图模型对象在哪里?Hooks 的创意很简单,但在跨调用维护本地状态的理念上彻底不同。但从架构的角度来看它仍然是以前的理念。咱们能够将其视为简单的语法级别更改。咱们都知道 JavaScript 语言中的类很糟糕,常常使人困惑,让开发人员很难编写干净的代码。Hooks 摆脱了类,从而解决了这个问题。
惟一改变的是视图模型的概念。不管是带有 Hooks 仍是函数组件的有状态组件,咱们均可以假设组件的视图模型对象是它的词法语境(Lexical Context) 或闭包。该组件接收的全部变量、Hooks 值或 props 共同造成其视图模型。其余框架也采用了这一理念。
当代架构的下一阶段看起来函数组件就是将来趋势。不过我不会说 Hooks 是一个功能齐全的长期解决方案(听起来好奇怪),但在语法层面上它很优雅,而且能够缓解古老的类问题。若是你不认为语法很重要,请看Svelte:https://svelte.dev/。
与 Web 应用程序相关的每项新技术都会在某种程度上影响前端应用程序。目前有三种趋势——GraphQL、*** 和编译器,这里必须具体介绍一下才算完整。
GraphQLGraphQL 是一种服务端查询语言。你可能已经看过有人说它取代了 REST,但事实并不是如此。当咱们谈论 REST 时,它是一种元模式。在概念层面,它采用面向资源的架构来定义应用程序域模型。在实现层面,它使用 HTTP 协议的语义来交换这些资源,以赋予 Web 共享信息的方式。
现代业务需求很复杂,许多工做流程不能简单地做为 HTTP CRUD 类比的资源公开。这就是 REST 比较尴尬的地方。GraphQL 旨在消息传递级别替换 REST 的纯 HTTP 协议。GraphQL 提供了本身的消息传递封装,能够被 GraphQL 服务器理解,而且还支持查询服务端资源(域模型)。
可是 GraphQL 客户端实现的反作用是,GraphQL 已经开始侵占状态容器的职责。咱们来看基本的事实,那就是客户端的模型只是服务端模型的一个子集,前者专门针对 UI 操做标准化,那么像 Redux/Flux 这样的状态容器只是在客户端缓存数据而已。
GraphQL 客户端内置了缓存支持,可跨多个请求来缓存。
这个简单的事实让开发人员能够省掉许多与状态管理相关的样板代码。在宏观层面,它的形态仍然有待观察。如下帖子详细描述了具体的机制:
GraphQL是怎样取代Redux的。
https://hackernoon.com/how-graphql-replaces-redux-3fff8289221d?source=post_page-----fb5b500b0231----------------------
用React Apollo瘦身咱们的Redux代码。
https://blog.apollographql.com/reducing-our-redux-code-with-react-apollo-5091b9de9c2a?source=post_page-----fb5b500b0231----------------------
必定要探索 GraphQL,由于它是将来趋势。
***——服务端渲染过去服务端 MVC 是很是重要的,服务器会生成静态或动态 HTML 页面,这些页面很容易被搜索引擎抓取。因为客户端框架能够提供出色的用户体验,咱们正逐渐在浏览器上渲染全部内容。彻底客户端渲染的应用程序在请求服务器时返回的典型 HTML 页面几乎是一个空页面:
SPA 应用程序的初始 HTML 文件——几乎是空的!
通常来讲这没什么问题,但在构建电子商务网站时遇到了麻烦,由于这类网站须要较快的加载速度和对 SEO 友好的可抓取内容。麻烦还不止于此,移动电话的互联网链接速度也很慢,而一些入门级设备的硬件也不好。搜索引擎对彻底客户端渲染的应用程序抓取的能力有限。
为了缓解这个问题,老式的 ***——服务端渲染 又回来了。有了 ***,咱们能够在服务器上渲染客户端 SPA,合并状态,而后将完整渲染的页面发送到客户端。它减小了应用程序页面的初始加载时间,从而提升了网站的响应速度。
*** 是下一步进化,它填补了客户端和服务端之间的鸿沟。
因为客户端代码是 JavaScript,咱们须要服务端的等效引擎来执行 JS 代码。Node.js 做为 JavaScript 引擎是执行 *** 的服务端技术栈的事实标准。虽然 *** 设置可能变得很是丑陋和复杂,但许多流行的框架已经提供了一流的工具和更高级别的框架,为 *** 提供了很是流畅的开发人员体验。
*** 完全改变了咱们的平常开发工做流程。咱们比客户端——服务端抽象更进一步。它引入了强制的三层架构,其中基于 Node.js 的服务器是关键的中间件。但从架构的角度来看——在数据流和分工层面全部这些都是相同的。*** 既不引入新的数据流,也没有改变已有的存在。
编译器时代:Svelte——编译器仍是缩小的框架?我不知道该如何描述 Svelte才好。我能说的是——当用户启动 Web 应用程序时,框架就在浏览器中运行。框架提供的转换抽象有其运行时成本。Svelte 是不同的。
因此 Svelte 是一个基于组件的框架,它展现了当代前端框架的全部特性,但同时它也是一个 编译器。编译器将源代码编译为高性能的命令式 JavaScript 代码。做为一个编译器,它能够作许多其余框架不能作的事情:与静态站点构建器同样,Svelte 在构建时运行,将组件转换为高效的命令式代码,从外部更新 DOM。
在不破坏语法的前提下将 DSL 整合到 JavaScript 中。
其宗旨是编写更少的代码。Svelte 证实编译器能够实现许多之前用纯 JavaScript 没法实现的功能。若是 Svelte 还不够,那么咱们能够用 Stencil.js,这是一个用于编写 Web 组件的 TypeScript+JSX 编译器。
其中,一些想法已经成为某种形式的主流思想——Angular AOT 编译器和 Vue 单文件组件等等。而后还有其余人将这种思想推向极致:
http://imba.io/?source=post_page-----fb5b500b0231----------------------
Rich Harris 的这篇演讲很好地展现了 Svelte 的底层哲学,并与 React 作了主观对比:
https://docs.google.com/presentation/d/1PUvpXMBEDS45rd0wHu6tF3j_8wmGC6cOLtOw2hzU-mw/mobilepresent?slide=id.p
还有其余方法!伟大的单体!一样,编译器的前端开发前景也很光明。
虽然完整的客户端框架如今风靡一时,但它并非惟一的行事方式。Web 依旧是多样化的。仍然有许多应用程序是服务端驱动的,并且将继续这样作。
但这是否意味着它们的体验会不好?固然不是!架构的设计目标是支持产品,Basecamp 团队开发的框架 Stimulus就作得很好。要了解他们的理念能够查阅:
https://m.signalvnoise.com/the-majestic-monolith/?source=post_page-----fb5b500b0231----------------------
它是一个适度的框架,经过轻量级 JavaScript 提高后端渲染页面的交互性,同时采用最新实践和最新标准。Stimulus 一般与 Turbolinks并用,以建立一流的 SPA 用户体验。(我是 Basecamp 的老用户了,发现它比其余许多 SPA 应用程序都更精致。)
Stimulus 在某种意义上是不同的,由于它是经过 HTML 而非 JavaScript 驱动。状态在 HTML 而非 JavaScript 对象中维护。数据流很是简单:控制器附加到 DOM,它公开了能够附加到操做上的方法,从而执行进一步的操做。
你可能会想到,它很像 Backbone 和 Knockout 的时代——确实如此。目标很简单——为后端驱动 Web 提供的交互式前端框架。惟一的不一样是 Stimulus 采用了现代社区标准和实践。
Strudel.js是另外一个相似理念的适度框架。在 2019 年,咱们能够使用像 RE:DOM 这样的当代 DOM 库。
虽然它们可能没法解决当代前端框架面临的全部问题,但它们给 JavaScript 审美疲劳的世界带来了一丝喘息之机。
总结只有一个词能用来描述 GUI 架构——华丽。虽然对于前端软件开发来讲,MVC 做为一种模式已经逝去了,但原则是永恒不变的。
咱们从原始的 MVC 开始探索了著名的桌面模式。而后咱们转到 Web 应用程序并使用相同的原则来获得了流行的模式。以后咱们转向早期的独立客户端模式,最后全面讨论了 SPA 框架。
重要的一点是,今天的前端框架是面向组件的,它们将 MVC/MVVM 关注点做为一个层面,同时要处理新的环境和挑战。
最后,咱们浏览了一遍前端架构的新面孔,包括 JavaScript 驱动的 *** 和 GraphQL 的崛起。同时咱们跳过了许多使人兴奋的新技术,如 HTTP2 和 WebAssembly 等,它们能够改变前端架构的将来,改变咱们对应用程序的见解。
因为全部这些模式涉及的术语都有所重叠,而且一般是由社区各自独立开发的,所以很难根据进化时间来定义明确的线性时间轴。有时将不一样的模式联系在一块儿是有问题的。此外为了简单起见,咱们能够自由地描述某些概念,不用特别研究它们的细节。没有现成的命名法则可用,因此有些理念我用了本身发明的术语。
Web 应用程序拓扑是 Web 应用程序架构的另外一个关系密切的领域。拓扑一般在技术栈选择、安全约束和性能等方面对前端开发产生深远影响。所以这将是下一篇文章的主题。
咱们但愿本文可以帮助你更好地理解前端架构的元素。求分享扩散!
英文原文: https://blog.webf.zone/contemporary-front-end-architectures-fb5b500b0231
活动推荐IJKPlayer 是 BiliBIli 开源的一款基于 ffmpeg 的优秀的播放器,它支持 Android 和 iOS 双平台, API 易于集成,编译配置可裁剪,方便控制安装包大小,还支持硬件加速解码更省电......
最重要的一点是 IJKPlayer 还能够实现跨平台。此次 GMTC 全球大前端技术大会,咱们专门请到了 B 站移动技术部负责移动端 IJKPlayer 相关工做的资深开发工程师郑翰超,作此次技术分享,从 B 站内部视角看看 IJKPlayer 的前世此生,全面了解这款播放器的演进之路。扫描下方二维码或点击阅读原文,查看详情。
目前 GMTC 深圳站 8 折售票通道已经开启,详细请咨询:13269078023(同微信)。