1.2 单页应用
单页应用(single-page application,SPA)由覆盖了整个前端应用功能的一个或几个 JavaScript 文件组成,这些文件通常会被预先下载。Web 服务器或内容分发网络(content delivery network,CDN)返回 HTML 入口页后,单页应用会加载 JavaScript、CSS 和其他资源。使用单页应用有很多好处,比如客户端只需在应用的生命周期开始时下载一次代码,此后用户会话中的全部应用逻辑就都处于可用状态。
单页应用通常通过 API 来与后端(也叫服务器端)的持久层交换数据。此外,单页应用避免了客户端和服务器之间为了加载附加逻辑而导致的频繁通信,做到在应用的生命周期内可以立刻渲染所有视图。
上述这些能力都增强了用户体验,模拟了用户与移动设备或桌面的原生应用交互时的情况,用户无须等待太长时间就可以在应用中进行跳转。
另外,单页应用中的路由机制完全由客户端维护。这意味着每次更改视图时,应用都会更改 URL(uniform resource locator),以便用户分享页面链接或把 URL 加入书签后仍可以直接访问指定的页面。单页应用也让我们可以自由地决定如何在服务器端和客户端之间划分应用逻辑。比如,我们可以打造一个“胖客户端”和一个“瘦服务器端”,客户端用来存储逻辑,服务器端则用作持久层;或者打造一个“瘦客户端”和一个“胖服务器端”,逻辑主要交给服务器端,而客户端不执行任何智能逻辑,只是响应 API 返回的状态。
在过去的几十年里,一直有着对“胖客户端”和“瘦客户端”哪种方式更好的讨论,两种观点都曾成为主流。尽管有争论,但这两种方式都有各自的优缺点。哪种才是更好的选择往往取决于我们创建的应用类型。如果想创建一个跨平台的应用,那么选择“瘦客户端”和“胖服务器端”的组合非常合适。通过这种方式,一个功能只需设计一次就可以让部署在多种宿主环境的所有客户端响应存储在服务器端的应用状态。
而当创建桌面应用时,离线存储一些数据会是一个基本功能。在这种情况下,经常选择“胖客户端”和“瘦服务器端”的组合。我们不需要在两个地方管理业务逻辑,而只用客户端进行管理,使用服务器端同步用户数据。
然而,对于某些类型的应用来说,单页应用存在一些缺点。因为要下载整个应用,而不是只下载用户当前用到的部分,所以单页应用的首次加载时间可能比其他架构长。如果应用没有设计好,下载时间可能会成为严重问题,尤其是在网络连接不稳定的智能手机、平板电脑等移动设备上。
现在,我们可以采用多种方式在客户端直接缓存应用内容,从而缓解上述问题。除了代码拆分或 JavaScript 模块的懒加载,值得一提的技术当属渐进式 Web 应用(progressive Web application,PWA)。渐进式 Web 应用基于 Service Worker 提供了一系列新的能力。Service Worker 是浏览器在后台独立于网页运行的脚本,用于提供离线体验或推送通知等功能。
有了 Service Worker,就可以使用浏览器原生 API 为 Web 应用制定缓存策略。这种模式被称为离线优先或缓存优先,它是 Web 应用向用户提供内容的最流行的策略。如果一个资源已被缓存并可离线使用,就先使用缓存,而不是直接从服务器下载;如果这个资源还没有被缓存,就下载并缓存起来,以便将来使用。虽然这看起来很简单,但是对于提升 Web 应用的用户体验非常有用,特别是在移动设备上的用户体验。
单页应用的另一个缺点与 SEO(search engine optimization)有关。当爬虫(一种系统地爬取网页并创建数据索引的程序)试图爬取单页应用时,它很难为网页的内容创建索引,除非我们提供了其他方式来让爬虫获取内容。
通常情况下,当想让单页应用更好地被爬虫爬取内容时,我们往往会专门为爬虫制定一个特殊策略。比如,当请求 Netflix 的 Web 应用的用户代理被识别为爬虫时,Netflix 就会将定位机制退化,避免根据 URL 里的国家信息来决定提供什么内容。这是一个非常机智的策略,因为爬虫经常只在同一个地理位置爬取全世界的网页。
一口气下载应用的所有逻辑也会成为单页应用的一个缺点,因为如果代码实现有问题或者没有正确处理不再使用的对象,那么当用户从一个视图跳转到另一个视图时,可能会导致潜在的内存泄漏。这在大型应用中可能是一个严重问题,因为可能需要花费几天或几周的时间重构和改进,才能让单页应用的代码正常运行。如果加载单页应用的硬件资源有限,比如智能电视或机顶盒,那么情况可能会更糟。我经常看到一些应用在四核的 MacBook Pro 上流畅运行,而在低端设备上运行时却一塌糊涂。
单页应用的最后一个缺点是在团队组织方面。当大型单页应用由多个团队在同一个代码库中共同开发时,这个应用的不同部分可能会混合使用各种方法和策略,以至于让团队成员感到困惑。团队合作时的沟通成本经常是应用开发工作的隐性成本。
复杂的项目导致团队效率低下的问题经常会被忽略,这并不是因为团队没有足够的开发能力,而是因为公司的结构或组织架构让他们的能力无法以最好的方式表现出来,以至于在开发一个新功能的过程中,执行效率低下,产生了外部依赖,甚至出现冲突摩擦。此外,因为许多关键性决定可能不是出自开发人员,或者在开发人员加入公司时的几个月前,甚至几年前,这个大型单页应用的代码库就已经存在了,所以他们会缺少主人翁精神。
所有这些情况都不会在公司每月的费用清单中有所体现,但是它们可能会影响团队的产出,因为复杂的代码库会大大地降低团队的交付能力。