Resumable vs. Hydration
Qwik 应用程序的一个关键概念是它们可以从服务器端渲染的状态中恢复。解释可恢复性的最好方式是理解当前一代框架是如何可重放(hydration)的。
当 SSR/SSG 应用程序在客户端启动时,它需要客户端上的框架恢复三个信息:
- 监听器 - 定位事件监听器并将其安装在 DOM 节点上,使应用程序具有交互性。
- 组件树 - 构建表示应用程序组件树的内部数据结构。
- 应用程序状态 - 恢复在服务器上获取或保存在
store
中的任何数据。
总体而言,这被称为hydration。所有当前一代的框架都需要这一步骤来使应用程序具有交互性。
hydration 是昂贵的,原因有两点:
- 框架必须下载与当前页面相关的所有组件代码。
- 框架必须执行与页面上的组件相关联的模板,以重建监听器位置和内部组件树。
Qwik 不同的地方在于它不需要 hydration 来在客户端上恢复应用程序。不需要 hydration 是使 Qwik 应用程序启动瞬间完成的原因。
其他框架的 hydration 重放了客户端上的所有应用程序逻辑。Qwik 相反,在服务器上暂停执行,然后在客户端上恢复执行。
引入可恢复性
可恢复性是指在服务器上暂停执行,然后在客户端上恢复执行,而无需重放和下载所有应用程序逻辑。
一个好的思维模型是,Qwik 应用程序在其生命周期的任何时刻都可以被序列化并移动到不同的 VM 实例(从服务器到浏览器)。在那里,应用程序只需在序列化停止的地方继续执行。不需要 hydration。这就是为什么我们说 Qwik 应用程序不进行 hydration,而是进行恢复。
为了实现这一点,Qwik 需要以与无代码启动兼容的方式解决 3 个问题(监听器、组件树、应用程序状态)。
监听器
没有事件监听器的 DOM 只是一个静态页面;它不是一个应用程序。如今,所有网站的标准都具有相当高的交互性,因此即使是看起来最静态的网站也充满了事件监听器。这些包括菜单、悬停、展开详情,甚至是完全交互式的应用程序。
现有的框架通过下载组件并执行其模板来解决事件监听器问题,以收集附加到 DOM 上的事件监听器。当前的方法存在以下问题:
- 需要急切下载模板代码。
- 需要急切执行模板代码。
- 需要急切下载事件处理程序代码(以进行附加)。
上述方法不可扩展。随着应用程序变得更加复杂,需要急切下载和执行的代码量与应用程序的大小成比例增长。 这对应用程序的启动性能产生负面影响,从而影响用户体验。在其他框架中,按需加载应用程序的代码块本身就成为一个开发任务,需要耗费时间和精力。在 Qwik 中,按需加载是可恢复架构的自然结果。
Qwik 通过将事件监听器序列化到 DOM 中来解决上述问题,如下所示:
<button on:click="./chunk.js#handler_symbol">click me</button>
Qwik 仍然需要收集监听器信息,但这一步骤是作为 SSR/SSG 的一部分完成的。SSR/SSG 的结果然后被序列化为 HTML,因此浏览器无需执行任何操作即可恢复执行。请注意,on:click
属性包含了恢复应用程序所需的所有信息,而无需急切执行任何操作。
- Qwikloader 设置了一个全局监听器,而不是每个 DOM 元素都有一个独立的监听器。这一步可以在没有应用程序代码的情况下完成。
- HTML 包含了一个指向代码块和符号名称的 URL。该属性告诉 Qwikloader 下载哪个代码块,并从代码块中检索哪个符号,然后执行它。
- 最后,为了使上述所有操作成为可能,Qwik 的事件处理实现理解异步性,这使得它可以自动惰性加载闭包。
组件树
框架使用组件树。为此,框架需要完全了解组件树,以知道哪些组件需要重新渲染以及何时重新渲染。如果查看现有框架的 SSR/SSG 输出,组件边界信息已被破坏。通过查看生成的 HTML,无法知道组件边界的位置。为了重新创建此信息,框架会重新执行组件模板并对组件边界位置进行记忆化。重新执行就是 hydration。hydration 是昂贵的,因为它需要下载组件模板并执行它们。
Qwik 在 SSR/SSG 的一部分中收集组件边界信息,然后将该信息序列化为 HTML。结果是,Qwik 可以:
- 重新构建组件层次结构信息,而无需实际存在组件代码。组件代码可以保持惰性加载。
- Qwik 只能对需要重新渲染的组件进行惰性操作,而不是一次性操作所有组件。
- Qwik 收集存储和组件之间的关系信息。这创建了一个订阅模型,通知 Qwik 哪些组件需要重新渲染以响应状态更改。订阅信息也被序列化为 HTML。
应用程序状态
现有的框架通常有一种方式将应用程序状态序列化为 HTML,以便在 hydration 的过程中恢复状态。在这方面,它们与 Qwik 非常相似。然而,Qwik 将状态管理更紧密地集成到组件的生命周期中。实际上,这意味着组件可以独立于组件的状态进行延迟加载。这在现有的框架中很难实现,因为组件的 props 通常由父组件创建。这会引发连锁反应。为了恢复组件 X,必须先恢复其父组件。Qwik 允许任何组件在没有父组件代码的情况下恢复。
序列化
最简单的序列化方式是通过 JSON.stringify
进行序列化。然而,JSON 有一些限制。Qwik 可以克服一些限制,但有些限制无法克服,并对开发人员的操作产生限制。了解这些限制在构建 Qwik 应用程序时非常重要。
Qwik 解决的 JSON 限制:
- JSON 生成一个有向无环图(DAG)。这意味着被序列化的对象不能具有循环引用。这是一个很大的限制,因为应用程序状态通常是循环的。Qwik 确保在序列化对象图时,循环引用得到正确保存和恢复。
- JSON 无法序列化某些对象类型。例如,DOM 引用或日期。Qwik 的序列化格式确保这些对象可以正确地序列化和恢复。以下是可以使用 Qwik 进行序列化的类型列表:
- DOM 引用
- Promises(参见 resources)
- 函数闭包(如果包装在 QRL 中)
- 日期
URL
对象Map
和Set
实例。
Qwik 无法解决的 JSON 限制:
对于无法进行序列化的情况,代码应该仅在客户端上运行(仅在服务器和浏览器上运行的任务)。
以可序列化为目标编写应用程序
框架的可恢复性能力必须扩展到应用程序的可恢复性能力。这意味着框架必须为开发人员提供一种方式来以可序列化并在恢复时(无需重新启动)的方式来表达应用程序的 组件 和 实体。这要求应用程序在编写时考虑到可恢复性的约束条件。开发人员无法继续以堆为中心的方式编写应用程序,并期望更好的框架可以弥补这种次优方法。
开发人员必须以 DOM 为中心的方式编写他们的应用程序。这将需要改变行为并重新调整 Web 开发人员的技能。框架需要提供指导和 API,以便开发人员可以轻松地以这种方式编写应用程序。
可恢复性的其他好处
使用可恢复性的最明显好处是用于服务器端渲染。然而,还有其他次要的好处:
- 序列化现有的 PWA 应用程序,以便用户在返回应用程序时不会丢失上下文
- 改进的渲染性能,因为只需要重新渲染更改的组件
- 细粒度的按需加载
- 减少内存压力,特别是在移动设备上
- 现有静态网站的渐进式交互