Web
技术大概 25
年前开始萌芽,HTTP、HTML、CSS 和 JS 都是在九十年代中期首次被标准化的。直到如今,Web
演变成一个无处不在的应用平台。随着 Web
的发展,Web 应用程序的开发架构也在不断发展。现在有许多用于构建 Web
应用程序的核心架构,目前最流行的是单页应用 (SPA
),但我们正在逐渐过渡到一种新的改进架构来构建 Web
应用程序。
下面是一些主要的架构模式:
- 多页应用 (MPA)
- 渐进增强的多页应用(PEMPA)
- 单页应用 (SPA)
- 渐进增强的单页应用 (PESPA)
每种架构都有它的优点和痛点,但是往往架构的痛点会成为一个足以促使人们转向下一个架构的核心动力,在技术选型时,我们需要综合考虑。
无论我们怎么构建我们的应用程序,总绕不过需要在服务器上运行代码。其实这些架构的最大区别就是代码所在的位置。下面我们就依次来看一下,并观察代码的位置是如何随时间演进的。在分析每种架构时,我们会从以下几个角度考虑:
- 持久化(
Persistence
) - 从数据库中保存和读取数据 - 路由(
Routing
) - 根据 URL 切换模块 - 数据获取(
Data fetching
) - 从持久化存储中检索数据 - 数据变更(
Data mutation
)- 持久化中的数据变化 - 渲染逻辑(
Rendering logic
) - 向用户显示数据 - UI 反馈(
UI Feedback
) - 响应用户交互
注意:在后面的架构图中我们都会使用英文
当然,Web
应用程序的组成部分远不止这些,但这些部分是变化最多的部分,也是我们作为 Web
开发者花费大量时间的地方。根据不同的项目规模和团队结构,我们可能会处理所有这些类别的代码,也可能只处理其中的一部分。
多页应用 (MPA)
在早期,浏览器的功能比较简单,这是当时在 Web
上运行的唯一架构。
MPA
我们编写的所有代码都存在于服务器上,只有客户端上的 UI反馈
代码由用户的浏览器处理。
MPA 架构
文档请求
MPA 文档请求
当用户在地址栏中输入 URL
时,浏览器会向我们的服务器发送请求。我们的路由逻辑将调用一个函数来获取数据,该函数会与数据库通信来检索数据。然后,渲染逻辑会使用此数据来生成将作为响应发送给客户端的 HTML
。一般来讲,浏览器都会向用户提供一些处理中状态的反馈(比如 favicon
位置的 loading
)。
变更请求
MPA 变更请求
当用户提交表单时,浏览器会将表单内容序列化为发送到我们服务器的请求,我们的路由逻辑会调用一个函数来更新数据库。然后它就会通知浏览器进行重定向,浏览器会触发一个新的 GET
请求来获取新的 UI
(然后就和上一步用户输入 URL
的结果一样了)。
注意:成功的变更会发送一个重定向的响应,而不仅仅是发送一个新的 HTML
,这一点很重要。浏览器的历史堆栈中会有一个 POST
请求,点击后退按钮会再次触发这个 POST
请求(想知道为什么应用程序有时会显示:“不要点击后退按钮!!” 是的,就是这个原因。)。
MPA 的优缺点
MPA
的心智模型很简单,但当时我们对它并不看好。虽然在请求中主要由 Cookie
处理一些状态和复杂的流程,但在大多数情况下,一切都发生在请求/响应周期的时间内。
缺点:让诸如焦点管理之类的操作变得苦难,具有动画效果的页面切换几乎不太可能,用户体验很差。
值得注意的是,随着即将推出的 page transitions API
,Web
平台不断改进,给 MPA
架构带来了更多可能性。
https://developer.chrome.com/blog/shared-element-transitions-for-spas/
渐进增强的多页应用 (PEMPA)
渐进式增强的理念是:我们的 Web
应用程序应该是功能性的,对所有 Web
浏览器都应该是可访问的,然后利用浏览器的任何额外功能来增强体验。该术语由 Nick Finck
和 Steve Champeon
于 2003
年创造。
渐进增强是我们的 Web
应用程序应该是功能性的并且所有 Web 浏览器都可以访问的想法,然后利用浏览器具有的任何额外功能来增强体验。该术语由 Nick Finck 和 Steve Champeon 于 2003 年创造。
XMLHttpRequest
最初由 Microsoft
的 Outlook Web Access
团队于 1998
年开发,但直到 2016
年才标准化(你相信吗!?)。当然,这从未阻止过浏览器供应商和 Web
开发者。AJAX
作为一个术语在 2005
年流行起来,很多人开始在浏览器中发出 HTTP
请求。
业务建立在这样一个理念上,即我们不必回到服务器去获取更多的数据来更新适当的 UI
。这样,我们就可以构建逐步增强的多页面应用了:
PEMPA
“哇!“ 你可能会想, “等一下… 这些代码是从哪里来的?” 因此,现在我们不仅要负责来自浏览器的UI反馈,我们还需要向客户端提供路由、数据获取、数据变更和渲染逻辑,而不仅仅是在服务器上已有的这些逻辑。“到底发生了什么事?”
好吧,是这样的。渐进增强背后的理念是,我们的基线应该是一个功能性的应用程序。特别是在 21
世纪初,我们不能保证用户使用的浏览器能够运行像 AJAX
这样花哨的新东西,或者他们在与应用程序交互之前能够在足够快的网络上下载我们的 JavaScript
。所以我们需要保持现有的 MPA
架构,只使用 JavaScript
来增强体验。
也就是说,根据我们所讨论的增强级别,我们可能确实需要编写几乎所有类别的代码,数据持久化除外(除非我们想支持离线模式)。
另外,我们还必须向后端添加更多代码,来支持客户端发出的 AJAX
请求。所以在两端都需要有更多的开发者。
这是 jQuery、MooTools
等的时代。
PEMPA 架构
文档请求
PEMPA 文档请求
当用户第一次请求 HTML
文档时,发生的事情和 MPA
示例中的一样。但是,PEMPA
还将通过包含用于增强功能的 <script>
标签来加载客户端 JavaScript
。
客户端导航
PEMPA 客户端导航
当用户在我们的应用程序中单击带有 href
的 anchor
元素时,我们的客户端数据获取代码会阻止默认的整页刷新行为并使用 JavaScript
更新 URL
。然后客户端路由逻辑会确定需要对 UI 进行哪些更新并手动执行这些更新,包括在数据获取库向服务端发出网络请求时显示任何 Loading
状态(UI 反馈)。服务器路由逻辑会调用数据获取代码从数据库中检索数据并将其作为响应(XML
或 JSON
)发送,然后客户端使用其渲染逻辑执行最终的 UI
更新。
PEMPA 变更请求
当用户提交表单时,我们的客户端数据变更逻辑会阻止默认的整页刷新和发布行为,使用 JavaScript
序列化表单并将数据发送到服务端。然后,服务器路由逻辑调用数据变更函数,与数据库交互以执行变更,并将更新的数据响应给客户端。客户端渲染逻辑将使用更新后的数据来更新 UI;在某些情况下,客户端路由逻辑会将用户发送到另一个地方,这会触发与客户端导航流程类似的流程。
PEMPA 的优缺点
通过引入客户端代码并将 UI 反馈的责任推给我们自己,我们确实解决了 MPA
的问题。我们有更多的控制权,可以给用户一种更像自定义应用程序的感觉。
但同时为了给用户提供他们想要的最佳体验,我们必须负责路由、数据获取、变更和渲染逻辑。这样做有几个问题:
- 阻止浏览器默认行为 - 在路由和表单提交方面,我们做得不如浏览器好。在此之前,保持页面上的数据是最新的从来都不是一个需要考虑的问题,但现在这在我们的客户端代码中占了一半以上。此外,竞争条件、表单重新提交和错误处理都是隐藏
bug
的好地方; - 自定义代码 - 有更多的代码需要管理,而我们以前不必编写这些代码。我知道相关性并不意味着因果关系,但我注意到,一般来说,我们的代码越多,我们的错误就越多