10.0 Client Side Rendering
本讲座涵盖了客户端渲染(Client-Side Rendering, CSR)、单页应用(Single Page Applications, SPA)以及 Web Socket 这三个核心概念。理解它们的工作原理、优缺点以及适用场景是本课程的重要考点。
1. 什么是客户端渲染及其工作原理(高层次理解)
到目前为止,我们看到的 JavaScript 主要响应浏览器中的本地事件,例如用户点击按钮、页面加载和鼠标移动等。然而,我们经常需要响应远程事件,比如有人给你发消息、点赞帖子等。此外,有时我们也需要利用服务器上的信息来动态响应本地事件,例如如果用户输入 4 月 1 日作为首选预约日期,我们希望立即向他们显示可用的可用预约时间。
处理这种需求的一种方式是将日期发送到服务器,让服务器重新构建整个页面,然后将整个页面发送回浏览器。但很多时候,我们只需要传输少量的数据(例如,只有几个字节)就可以实现页面的部分更新。客户端渲染正是为了解决这个问题而诞生的,其核心目标是使用服务器和客户端之间发送的最小数据量来更新页面。
在高层次上,客户端渲染的工作方式如下:
- 首先,服务器会将初始的 HTML、CSS 和 JavaScript 代码发送到客户端浏览器。与服务器端渲染不同,通常发送给每个客户端的初始模板是相同的,因此可以使用静态文件。Flask 项目中的 "static" 目录就是用来存放这些静态文件的。
- 客户端的 JavaScript 代码接收到页面后,可以操纵文档对象模型 (DOM) 来动态地构建或修改页面结构。
- 当用户进行交互(例如,点击按钮、输入文本)或需要从服务器获取数据时,客户端的 JavaScript 会使用 AJAX (Asynchronous JavaScript and XML) 技术向服务器发送异步请求。
- 服务器接收到 AJAX 请求后,会处理请求并返回所需的数据,通常是格式化的数据,比如 JSON 对象。
- 客户端的 JavaScript 接收到服务器返回的数据后,会解析这些数据,然后使用 JavaScript 和 DOM 来动态更新页面上的特定部分,而无需重新加载整个页面。
Wordle 游戏是一个很好的客户端渲染示例。当用户输入一个猜测时,客户端 JavaScript 会提取猜测,然后通过 AJAX 请求将其发送到服务器。服务器接收到猜测后,会对照秘密答案(为保证游戏完整性,答案存储在服务器端的文件中以实现持久化)计算猜测结果(一个包含 0、1、2 等数字的数组,代表字母的状态:正确字母正确位置、正确字母错误位置、错误字母)。服务器将结果打包成 JSON 对象发送回客户端。客户端接收到 JSON 后,解析数据,然后根据结果数组动态更新 DOM,例如通过添加 CSS 类来改变猜测字母格子的颜色(绿色、黄色或灰色)。
客户端渲染使得页面更新更加流畅和快速,因为它只传输少量数据并只更新页面的部分内容。然而,它也需要在客户端编写更复杂的 JavaScript 逻辑来管理页面状态和 DOM 操作。服务器和客户端之间需要就数据格式和含义达成一致,这实际上需要定义一个自定义的通信协议(例如 Wordle 中用 0、1、2 表示猜测结果)。
2. 客户端渲染与服务器端渲染的对比
- 服务器端渲染 (SSR):服务器负责大部分工作。它使用模板引擎(如 Jinja)结合特定数据来生成完整的 HTML 页面,然后将生成的 HTML 发送给浏览器。如果需要更新页面上的任何内容,通常需要重新生成并发送整个页面,这是非常重量级且昂贵的(就开发工作量和带宽而言)。每次更新都需要发送大量数据,导致延迟。
- 客户端渲染 (CSR):服务器发送一个基本的 HTML 骨架以及 CSS 和 JavaScript 文件。页面的大部分内容由客户端的 JavaScript 根据从服务器异步获取的数据动态生成和填充。页面更新通过发送少量数据并仅修改 DOM 的特定部分来完成。
主要区别和比较:
- 效率/带宽: CSR 通常比 SSR 更高效,因为它在页面更新时只传输少量必要的数据(如 JSON),而 SSR 需要传输整个页面的 HTML。服务器的负载也相对较小,可以响应更多客户端。
- 复杂性: CSR 需要在客户端编写更复杂的 JavaScript 代码来处理数据、更新 DOM 和管理应用程序状态。开发人员需要手动操作 DOM,而 SSR 可以利用模板引擎提供的便利。
- 初始加载时间: SSR 在服务器上预生成 HTML,通常可以更快地将内容呈现在用户屏幕上。CSR 需要先下载、解析和执行 JavaScript 代码,然后再获取数据并构建页面,这可能导致初始加载时间变长,特别是对于大型应用程序而言。
- 自定义协议: CSR 通常需要服务器和客户端之间定义自定义的数据交换协议。
- 响应性: CSR 可以提供更流畅、响应更快的用户体验,因为页面更新无需完整的页面重新加载。
需要注意的是,服务器端渲染和客户端渲染并非互斥,可以在同一个应用中结合使用。例如,可以使用 SSR 生成初始页面,然后使用 CSR 来处理后续的动态交互和更新。
3. 单页应用 (Single Page Applications, SPA)
单页应用是客户端渲染的一种极致应用。在 SPA 中,整个网站通常由一个 HTML 页面提供。所有的页面内容和视图切换都是通过客户端 JavaScript 动态加载和渲染来实现的。浏览器或客户端承担了大部分的逻辑和渲染工作,而服务器主要只负责提供数据(通常通过 API)。用户在 SPA 中浏览时,看起来可能在不同的“页面”之间切换,但实际上并没有导航到新的 URL(或 URL 会变化,但没有触发浏览器完整的页面重新加载)。
LMS 系统就是一个典型的单页应用示例。当你使用 LMS 时,即使在不同课程、不同页面之间切换,浏览器的 URL 可能变化,但页面并没有进行完整的重载。
单页应用的优点 (Pros of SPA):
- 减轻服务器负载: 客户端承担了大部分渲染和逻辑工作,服务器只需要响应数据请求,因此服务器负载降低,可以服务更多客户端。
- 更快的客户端响应(理论上): 一旦初始页面加载完成,后续的视图切换和内容更新无需等待服务器发送整个新页面,理论上可以提供更流畅、更快的用户体验。
- 内容与展示的真正分离: 服务器只关注数据内容,客户端(SPA)负责如何展示这些数据,这提供了一个清晰的抽象层。这种架构也使得使用同一个后端 API 为 Web 应用和原生移动应用提供数据成为可能。
单页应用的缺点 (Cons of SPA):
- 初始加载时间长: 需要传输大量的 JavaScript 代码到客户端并在浏览器中执行,这可能导致首次加载时间比传统的多页应用要长。动态渲染大量的 HTML 和 CSS 如果处理不当,在浏览器中可能会比较慢。
- 搜索引擎优化 (SEO) 问题: 传统的搜索引擎爬虫主要读取页面初始的 HTML 内容。SPA 首次加载时可能只有一个空的 HTML 骨架,大部分内容由 JavaScript 动态生成。由于爬虫通常不执行复杂的 JavaScript 代码或模拟用户交互,它们可能无法抓取和索引 SPA 的动态内容,导致 SEO 困难。
- 导航问题: 浏览器的前进和后退按钮、以及页面刷新可能会出现问题。浏览器可能无法正确跟踪 SPA 内部的状态变化,导致用户刷新页面后回到初始状态,而不是刷新前的“页面”。尽管可以通过修改浏览器历史记录 API 来模拟前进/后退功能,但这可能会导致行为不稳定。
根据讲座内容,单页应用带来了许多麻烦,特别是对于基于“页面”逻辑的应用来说。如果需要极度无缝和高度可控的用户体验,创建原生移动应用可能是更好的选择,而不是强行用 Web 技术构建 SPA。
课程项目要求: 请不要创建单页应用!项目的绝大部分应该使用 Jinja 模板和服务器端渲染来实现。
4. Web Socket 的目的
HTTP 请求对于获取动态内容很有用,但它们的设置相对重量级且昂贵。HTTP 请求本质上是单次事务:客户端发送请求,服务器响应,然后两者通常就忘记了这次交互(除非通过 Cookie 等方式维护状态)。
然而,许多 Web 应用依赖于实时交互,例如聊天应用、实时记分板、股票价格更新、天气预报或新闻推送。对于这类应用,不断地发送 HTTP 请求(例如,使用 AJAX 定期轮询服务器)效率很低,且无法实现真正的服务器向客户端的即时推送。
Web Socket 技术于 2011 年被标准化,旨在提供全双工(双向)通信。Web Socket 允许客户端的 JavaScript 与服务器建立一个持久的连接(数据流)。一旦连接建立,客户端和服务器就可以通过这个连接进行轻量级的双向数据传输,而无需反复建立和拆除 HTTP 连接。这使得在应用中实现实时通信成为可能。服务器可以主动向已连接的客户端推送消息,客户端则通过监听器架构立即响应这些推送。
在 Flask 中,Web Socket 通过 flask-socketIO
扩展包提供支持。socketIO
适用于传输轻量级实时数据,如聊天消息或分布式游戏中的事件更新。对于需要传输大量实时流数据(如视频或音频)的应用,通常使用 WebRTC 或其他库,因为它们可能支持点对点连接以减少服务器中转带来的延迟。
socketIO
架构中有一个“房间”的概念,用户或进程可以加入或离开房间。服务器可以向特定房间内的所有客户端广播消息。客户端和服务器通过发送和接收“事件”来交互,事件可以有预定义的名称(如 join
, status
, message
, leave
)或自定义名称。
5. Web Socket 的常见用例
Web Socket 适用于任何需要客户端与服务器之间进行实时、低延迟双向通信的场景:
- 实时聊天应用: 客户端发送消息到服务器,服务器通过 Web Socket 将消息广播给聊天室内的其他客户端。
- 多人在线游戏: 用于同步游戏状态、发送玩家操作等实时事件。
- 实时数据更新: 例如实时股票价格、天气信息、体育赛事比分直播等。
- 实时通知系统: 服务器可以即时将新消息、新事件等通知推送到客户端。
- 实时博客或活动流: 当有新内容发布时,可以立即推送到所有在线用户。
- 私有聊天和群组聊天: 通过结合用户认证、会话管理以及 Socket.IO 的命名空间和房间功能,可以实现用户之间的私聊和群组聊天。
课程项目要求: 在课程项目中,实现 Socket 是可选的,并没有专门为 Socket 分配分数。可以根据项目需求决定是否使用 Web Socket。
6. 课程项目与本讲座内容的关联
如前所述,课程项目的大部分内容应使用服务器端渲染和 Jinja 模板来实现。然而,项目必须展示您了解并知道如何使用 AJAX 请求,并在不重新加载页面的情况下与服务器传输数据。这部分将会有评分。
最自然的实现 AJAX 的地方是用户搜索功能。例如:
- 用户在搜索框中输入(部分)用户名。
- 客户端 JavaScript 发送一个 AJAX 请求到服务器,将输入的文本作为参数。
- 服务器查询数据库(或文件等)找到匹配的用户列表。
- 服务器将匹配的用户列表作为响应(例如 JSON 格式)发送回客户端。
- 客户端 JavaScript 接收到数据后,动态更新页面,显示匹配的用户列表供用户选择。