辛宝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 版本承諾向前兼容,對用戶來說影響不大,所以比較期待內部細節,這篇文章是系列文章的第一個,主要介紹極致壓縮編碼的細節,很多地方需要花更多時間去理解和消化,但是對下個版本充滿信心,真是一個有趣的框架。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。