辛宝Otto

辛宝Otto 的玄酒清谈

北漂前端程序员儿 / 探索新事物 / Web Worker 主播之一/内向话痨
xiaoyuzhou
email

速通 - Qwik 2.0 前瞻

速通 - 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 会有一些注释节点,包含了很多东西。

image

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

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。