Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

渐进式增强(与优雅降级)

Progressive Enhancement (and Graceful Degradation)

我在波士顿开车已经大约十五年了。如果你不了解波士顿,让我告诉你:马萨诸塞州拥有世界上一些最激进的司机(和行人!)。我学会了践行一种有时被称为“防御性驾驶”的习惯:假设在你有优先通行权的交叉路口,有人正准备突然切到你前面;随时准备好应对可能冲向马路的行人,并据此驾驶。

I’ve been driving around Boston for about fifteen years. If you don’t know Boston, let me tell you: Massachusetts has some of the most aggressive drivers (and pedestrians!) in the world. I’ve learned to practice what’s sometimes called “defensive driving”: assuming that someone’s about to swerve in front of you at an intersection when you have the right of way, preparing for a pedestrian to cross into the street at any moment, and driving accordingly.

“渐进式增强”是网页设计的“防御性驾驶”。或者更准确地说,是“优雅降级”,虽然它们是同一枚硬币的两面,或者是从两个不同方向进行的同一个过程。

“Progressive enhancement” is the “defensive driving” of web design. Or really, that’s “graceful degradation,” although they’re two sides of the same coin, or the same process, from two different directions.

在本文语境下,渐进式增强(Progressive enhancement)意味着从一个简单的 HTML 网站或应用开始,它能为任何访问你页面的用户提供服务,并逐渐通过添加功能层来增强它:CSS 用于样式设计,JavaScript 用于交互,WebAssembly 用于由 Rust 驱动的交互;如果某些特定的 Web API 可用且有需要,则使用它们来获得更丰富的体验。

Progressive enhancement, in this context, means beginning with a simple HTML site or application that works for any user who arrives at your page, and gradually enhancing it with layers of additional features: CSS for styling, JavaScript for interactivity, WebAssembly for Rust-powered interactivity; using particular Web APIs for a richer experience if they’re available and as needed.

优雅降级(Graceful degradation)意味着当这一叠增强功能中的某些部分不可用时,能够优雅地处理故障。以下是用户在你的应用中可能遇到的一些故障来源:

Graceful degradation means handling failure gracefully when parts of that stack of enhancement aren’t available. Here are some sources of failure your users might encounter in your app:

  • 他们的浏览器不支持 WebAssembly,因为需要更新。

  • 他们的浏览器无法支持 WebAssembly,因为浏览器更新仅限于较新的操作系统版本,而这些版本无法安装在设备上。(说的就是你,苹果。)

  • 出于安全或隐私原因,他们关闭了 WASM。

  • 出于安全或隐私原因,他们关闭了 JavaScript。

  • 他们的设备不支持 JavaScript(例如,某些辅助设备仅支持 HTML 浏览)。

  • JavaScript(或 WASM)从未送达他们的设备,因为他们走到户外并丢失了 WiFi。

  • 他们在加载初始页面后踏入了地铁车厢,随后的导航无法加载数据。

  • ……等等。

  • Their browser doesn’t support WebAssembly because it needs to be updated.

  • Their browser can’t support WebAssembly because browser updates are limited to newer OS versions, which can’t be installed on the device. (Looking at you, Apple.)

  • They have WASM turned off for security or privacy reasons.

  • They have JavaScript turned off for security or privacy reasons.

  • JavaScript isn’t supported on their device (for example, some accessibility devices only support HTML browsing)

  • The JavaScript (or WASM) never arrived at their device because they walked outside and lost WiFi.

  • They stepped onto a subway car after loading the initial page and subsequent navigations can’t load data.

  • ... and so on.

如果其中一个成立,你的应用还有多少功能可以运作?两个呢?三个呢?

How much of your app still works if one of these holds true? Two of them? Three?

如果答案类似于“95%……好吧,然后是 90%……好吧,然后是 75%”,那就是优雅降级。如果答案是“除非一切正常工作,否则我的应用会显示白屏”,那就是……意外的快速解体(rapid unscheduled disassembly)。

If the answer is something like “95%... okay, then 90%... okay, then 75%,” that’s graceful degradation. If the answer is “my app shows a blank screen unless everything works correctly,” that’s... rapid unscheduled disassembly.

优雅降级对 WASM 应用尤为重要,因为 WASM 是浏览器运行的四种语言(HTML、CSS、JS、WASM)中最新且最不可能被普遍支持的一种。

Graceful degradation is especially important for WASM apps, because WASM is the newest and least-likely-to-be-supported of the four languages that run in the browser (HTML, CSS, JS, WASM).

幸运的是,我们有一些工具可以提供帮助。

Luckily, we’ve got some tools to help.

防御性设计

Defensive Design

有一些实践可以帮助你的应用更优雅地降级:

There are a few practices that can help your apps degrade more gracefully:

  1. 服务端渲染(SSR)。 如果没有 SSR,如果没有加载 JS 和 WASM,你的应用根本无法工作。在某些情况下这可能是合适的(想想受登录限制的内部应用),但在其他情况下,它就是坏掉了。

  2. 原生 HTML 元素。 使用能实现你想要的功能且无需额外代码的 HTML 元素:用于导航的 <a>(包括指向页面内锚点的链接)、用于手风琴效果的 <details>、用于在 URL 中持久化信息的 <form> 等。

  3. URL 驱动的状态。 全局状态存储在 URL 中(作为路由参数或查询字符串的一部分)越多,页面的更多部分就可以在服务端渲染期间生成,并通过 <a><form> 进行更新,这意味着不仅导航,状态更改也可以在没有 JS/WASM 的情况下工作。

  4. SsrMode::PartiallyBlockedSsrMode::InOrder 乱序流(Out-of-order streaming)需要少量的内联 JS,但如果 1) 连接在响应中途断开,或者 2) 客户端设备不支持 JS,则可能会失败。异步流(Async streaming)将提供完整的 HTML 页面,但仅在所有资源加载之后。顺序流(In-order streaming)则会按照从上到下的顺序更早地开始显示页面的各个部分。“部分阻塞(Partially-blocked)”的 SSR 建立在乱序流的基础上,它通过替换服务端读取阻塞资源的 <Suspense/> 片段来实现。这会略微增加初始响应时间(由于 O(n) 的字符串替换工作),以换取更完整的初始 HTML 响应。对于那些“更重要”和“不那么重要”的内容之间有明显区别的情况,这可能是一个不错的选择,例如:博客文章对比评论,或者产品信息对比评论。如果你选择阻塞所有内容,你实际上就重新创建了异步渲染。

  5. 倚重 <form> 最近出现了一股 <form> 的复兴趋势,这并不令人意外。<form> 以易于增强的方式管理复杂的 POSTGET 请求的能力,使其成为优雅降级的强大工具。例如,在 <Form/> 章节中的例子在没有 JS/WASM 的情况下也能正常工作:因为它使用 <form method="GET"> 将状态持久化在 URL 中,它通过发起正常的 HTTP 请求来配合纯 HTML 工作,然后渐进式地增强为使用客户端导航。

  6. Server-side rendering. Without SSR, your app simply doesn’t work without both JS and WASM loading. In some cases this may be appropriate (think internal apps gated behind a login) but in others it’s simply broken.

  7. Native HTML elements. Use HTML elements that do the things that you want, without additional code: <a> for navigation (including to hashes within the page), <details> for an accordion, <form> to persist information in the URL, etc.

  8. URL-driven state. The more of your global state is stored in the URL (as a route param or part of the query string), the more of the page can be generated during server rendering and updated by an <a> or a <form>, which means that not only navigations but state changes can work without JS/WASM.

  9. SsrMode::PartiallyBlocked or SsrMode::InOrder. Out-of-order streaming requires a small amount of inline JS, but can fail if 1) the connection is broken halfway through the response or 2) the client’s device doesn’t support JS. Async streaming will give a complete HTML page, but only after all resources load. In-order streaming begins showing pieces of the page sooner, in top-down order. “Partially-blocked” SSR builds on out-of-order streaming by replacing <Suspense/> fragments that read from blocking resources on the server. This adds marginally to the initial response time (because of the O(n) string replacement work), in exchange for a more complete initial HTML response. This can be a good choice for situations in which there’s a clear distinction between “more important” and “less important” content, e.g., blog post vs. comments, or product info vs. reviews. If you choose to block on all the content, you’ve essentially recreated async rendering.

  10. Leaning on <form>s. There’s been a bit of a <form> renaissance recently, and it’s no surprise. The ability of a <form> to manage complicated POST or GET requests in an easily-enhanced way makes it a powerful tool for graceful degradation. The example in the <Form/> chapter, for example, would work fine with no JS/WASM: because it uses a <form method="GET"> to persist state in the URL, it works with pure HTML by making normal HTTP requests and then progressively enhances to use client-side navigations instead.

框架还有一个我们尚未见过的特性,它建立在表单的这些特性之上,用以构建强大的应用:<ActionForm/>

There’s one final feature of the framework that we haven’t seen yet, and which builds on this characteristic of forms to build powerful applications: the <ActionForm/>.