速通 - Qwik 2.0 前瞻
原始信息
原文链接: 迈向 Qwik 2.0:更轻、更快、更好 发布于 2024-02-10
包含的信息
qwik 的是坚定站在 HTML 优先 HTML-first 的路线上,页面的交互会进行彻底的懒加载:官方称之为 可恢复性 resumability。
站在 2.0 版本更新的节点上,回顾 1.0 的可恢复性是有需要改进的地方。qwik 需要在 html 内容中添加信息,实现 html 和 js 逻辑的映射关系。
具体做了三个事情:
- 侦听器位置 Listener location 。在 html 上添加特定的属性,实际东西不多,改进可能性不大
- 组件边界 Component boundaries 。 本次的核心
- 应用的状态。也想调整,在后续讨论
所有的改动,都是向后兼容的,对用户不会产生影响。
Web 应用无关具体的框架,都是抽象的逻辑和 DOM Tree 建立关系。
举例子
源码
举例子,一个 qwik 组件,使用 jsx 语法,特定的函数 component$
进行包裹,实现一个计数器。
import { component$, useSignal } from '@builder.io/qwik';
export const Counter = component$(() => {
const count = useSignal(123);
return (
<>
Count: {count.value}!
<button onClick$={() => count.value++}>+1</button>
</>
);
});
export const Layout = component$(() => {
return (
<main>
<Slot />
</main>
);
});
// 页面中这样调用 <main> <Counter/> </main>
html 产物
可以打开 Qwik 的官方 Playgournd https://qwik.dev/examples/introduction/hello-world/ 把上面内容替换到 app.tsx
里面。
输出的 html 会有一些注释节点,包含了很多东西。
<div q:container="paused" q:render="static-ssr" q:version="dev"
q:base="/build/" q:locale q:manifest-hash="dev">
<main>
<!--qv q:s q:sref=5 q:key=-->
<!--qv q:id=7 q:key=xYL1:zl_0-->
<!--qv q:key=H1_0-->
Count:
<!--t=8-->123<!---->!
<button on:click="..." q:id="9">
+1
</button>
<!--/qv-->
<!--/qv-->
<!--/qv-->
</main>
<script type="qwik/json">{...}</script>
</div>
注释节点 1
首先是第一个注释节点 <!--qv q:s q:sref=5 q:key=-->
源码对应 Layout
组件,实现 slot
功能。用来路由内容
注释节点 2
第二个注释节点 <!--qv q:id=7 q:key=xYL1:zl_0-->
表示 上面的 Counter 组件。如果有 props 还会关联 qwik/json
内容。
原文比较绕,写的不容易理解:
- The additional attributes are used to cross-reference the props with this virtual node.
- 额外的属性用于与这个虚拟节点交叉引用 props - 也就是和 props 建立联系,这个 demo 的 props 没有
- A key point of resumability is that one should be able to re-render a component without the parent component needing to be resumed as well.
- 可恢复性的一个关键点是,应该能够重新渲染一个组件,而不需要恢复父组件。
- But components get props from the parent, so the props need to be serialized and recoverable so that this component can render independently of others.
- 但是组件从父组件获得 props,因此 props 需要序列化和可恢复,以便该组件可以独立于其他组件渲染。
- Qwik needs a virtual node for that.
- Qwik 需要一个虚拟节点。
也就是说,这个 qwik 组件需要和父组件解耦实现独立重新渲染,但是也要依赖父组件提供的 props 信息。
注释节点 3
第三个节点 <!--qv q:key=H1_0-->
标记 jsx 的 <></>
节点。
注释节点 4
注释节点 <!--t=8-->123<!---->
标记要更新的文本节点。
其他特殊属性
q:id
/ q:sref
/ t
这三个用来和状态做关联。
看着节点多,实际上也都有用。
有什么问题呢?
有多余内容。实际运行时候, Counter 组件永远不会下载到浏览器,因为后续交互不会增加、删除 DOM,只是更新操作 qwik 自己就可以更新 DOM,所以 Counter 组件的虚拟节点不是很必要。
如果是流式传输的话,在下载渲染的过程中,其他组件可能会影响 Counter ,所以只能先留着注释节点,整个 app 处理完毕,才知道这个组件是静态的,这个虚拟节点是不必要的。
对数据进行序列化之后需要有 ID 进行关联映射。但是在流式传输 html 时候, qwik xxx
2.0 会怎么改进?
- 所有人类不可度的数据都放到 html 的最后,一切为了更快渲染 UI
- 更有效的虚拟节点编码方案
- 让可恢复性算法进一步懒加载,只关注用户输入的虚拟节点
文章给出了新版的输出内容,输出结果很干净,完全没有多余的注释节点了,但是等价于上面的输出。
<div q:container="paused" q:render="static-ssr" q:version="dev"
q:base="/build/" q:locale q:manifest-hash="dev">
<main>
Count: 123!
<button on:click="...">+1</button>
</main>
<script type="qwik/state">[...]</script>
<script type="qwik/vnode">!{{HDB1}}</script>
</div>
不再把虚拟节点信息和 html 混合了,现在全部放到 qwik.vnode
内,一切为了更快渲染 UI
那岂不是丢失了辅助信息,那怎么知道 123
是会更新的?特别注意,123 前后都是纯字符串!
结论是辅助的节点信息仍然存在,看 qwik/vnode
内容,所有的节点信息压缩成了 9 个字符:
!{{{HDB1}}
。
这 9 个字符怎么来的
这 9 个字符怎么来的,需要考虑什么情况?这是一个技术问题。
文档这样说
We use the document.createTreeWalker API to retrieve all DOM nodes in depth in the first order.
我们使用 document.createTreeWalkerAPI 来检索所有 DOM 节点的深度在第一个顺序。
关键词:
document.createTreeWalker
不熟悉,是一个 api 用来遍历所有的 DOM 节点,先忽略,后续补充depth-first
通过序列号识别任何一个节点,这样就不用分配 ID 了。
那么这个神秘的 9 个字符有什么含义呢? !{{HDB1}}
拆解来看,请再次看渲染结果
// source
<main>
<>
Count: {count.value}!
<button onClick$={() => count.value++}>+1</button>
</>
</main>
// output
<main>
Count: 123!
<button on:click="...">+1</button>
</main>
!
编码过程要跳过多少个元素才能到达<main>
{
第一个花括号,表示<main>
元素有一个虚拟元素方 props{
第二个花括号,表示组件包含一个<></>
虚拟节点H
表示索引 index=7 的字母abcd efg h
,这表示实际渲染的Count: 123
第七个字母位置。也就是空格之后,123
开始之前,范围覆盖的内容是Count:
(冒号后面空格) ,共计 7 个字符。这个位置是第一个节点D
索引 index=3 表示123
之后的光标位置是新的 text 节点B
索引 index=1 对应!
1
表示消费的元素数量represents the number of elements to consume
是 1,也就是<button>
绝了,似懂非懂,就是拿大写字母做标记不超过 26 长度范围的内容,继续配合小写字母可以无限拓展。
感叹,细节先不纠结了,反正就是可以任意标记节点的起始位置了。的确可以省去虚拟节点了,这也太追求极致了吧!
优势
因为虚拟节点的编码在最后,所以等到解码执行时候可以安全无虞地知道组件的所有细节。
这将进一步减少发送的超文本标记语言的数量。由于新的编码使用深度优先索引,因此不会留下额外不需要的 ID。
额外优化
文章主要介绍编码方面的优化,运行时也有优化,主要做说明,使用数组而不是对象存储数据,数组可以自由增加数据、使用单个数组存储数据的话减轻内存压力。另外的优点是,访问是 O1 访问速度快。
其他的有点没看懂,先放着。
感受
qwik 一直以极致渲染性能著称,这次 2.0 版本承诺向前兼容,对用户来说影响不大,所以比较期待内部细节,这篇文章是系列文章的第一个,主要介绍极致压缩编码的细节,很多地方需要花更多时间去理解和消化,但是对下个版本充满信心,真是一个有趣的框架。