可恢复性
Qwik 不做水合,而是把服务端状态序列化进 HTML,在浏览器从断点继续。
可恢复性(resumability)的核心是一句话:在服务端暂停执行,在浏览器从暂停处继续。应用不需要在客户端重放服务端做过的事。
要理解它,先看水合必须恢复的三件东西:监听器、组件树、应用状态。Qwik 用序列化逐个解决,因此浏览器可以在没有任何应用代码的情况下启动。
01监听器:写进 DOM#
常规框架要下载组件、执行模板,才能知道哪个 DOM 节点该绑定哪个函数。Qwik 在 SSR 阶段就把监听器信息写进属性,浏览器只需要一个全局监听器来代理事件:
<button q-e:click="./chunk.js#s_LToOrb0bX0M">
click me
</button>Qwikloader 先在 document 上注册一个通用监听器。用户点击时,它从属性里读出 chunk 与符号名,按需下载并执行。事件处理函数不是页面启动的一部分,而是交互的结果。
02组件树:边界信息随 HTML 一起到达#
水合需要知道组件边界,才能决定状态变化时重渲染谁。常规 HTML 丢失了这些信息,所以框架必须重新执行模板。Qwik 把组件层次、属性与 DOM 的对应关系序列化进 HTML,浏览器不需要执行组件就能恢复结构。
更重要的是,Qwik 把状态与 DOM 的订阅关系也写进了 HTML。当某个 signal 改变时,Qwik 知道需要更新哪几个文本节点或属性,而不是重跑整个组件。
03状态:不只是 JSON#
JSON 无法表达循环引用、Date、Map、Set、Promise、DOM 引用和函数闭包。Qwik 的序列化格式支持这些类型,因此 store、资源与事件闭包可以跨过服务端与浏览器的边界。
| 可以序列化 | 不能序列化 |
|---|---|
| 基本类型、对象、数组 | 自定义 class 的实例与原型 |
| Date、URL、Map、Set | Streams |
| Promise、资源 | 未包装为 QRL 的函数 |
| QRL 包装的闭包 | DOM 节点之外的宿主对象 |
component$(() => {
const label = '可序列化'; // const + 可序列化
return <button onClick$={() => console.log(label)}>log</button>;
});04为什么这能加速启动#
- 浏览器不需要下载组件模板就能让页面响应。
- 每个组件、任务、事件都可以独立恢复,父组件代码不必存在。
- 状态变化只更新订阅它的 DOM,减少内存压力与重渲染。
- 同一份序列化状态可以被缓存、传输,甚至用于离线恢复。
05写应用时的约束#
可恢复性不只属于框架,也属于应用。凡是需要跨过服务端与客户端边界的状态,都应该以可序列化的形式表达。无法序列化的对象可以留在客户端,用 noSerialize() 标记,或者放进只在浏览器运行的 useVisibleTask$。
下一页渐进式加载会解释:既然一切都是符号,Qwik 如何决定什么时候加载哪个符号。