辛宝的玄酒清谈!
1915 words
10 minutes
速通 - Qwik 2.0 前瞻
2024-02-15

速通 - Qwik 2.0 前瞻

速通 - 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 版本承诺向前兼容,对用户来说影响不大,所以比较期待内部细节,这篇文章是系列文章的第一个,主要介绍极致压缩编码的细节,很多地方需要花更多时间去理解和消化,但是对下个版本充满信心,真是一个有趣的框架。

速通 - Qwik 2.0 前瞻
https://ijust.cc/posts/quick-review-qwik2-encode/
Author
辛宝 Otto
Published at
2024-02-15