天天看點

blockdom:世界上最快的虛拟 dom 庫!

作者:進階前端進階

大家好,很高興又見面了,我是"進階前端‬進階‬",由我帶着大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點贊、收藏、轉發!

blockdom:世界上最快的虛拟 dom 庫!

今天給大家帶來的主題是 blockdom,即号稱是世界上最快的虛拟 dom 庫。話不多說,直接進入正題!

1.什麼是 blockdom

blockdom 号稱是世界上最快的虛拟 dom 庫。

blockdom 是一個非常快速的虛拟 dom 庫,主要賣點是它不是逐個元素地展示 DOM,而是逐塊地展示 DOM,其中 block 是一個元素及其所有靜态内容和一些特殊标簽來表示的動态内容。 這允許 blockdom 在塊上使用 cloneNode(true) 并加速 diff 過程,因為這種情況下 vdom 樹要小得多。

blockdom:世界上最快的虛拟 dom 庫!

blockdom 支援塊、片段(Fragment)、管理合成事件處理程式(Synthetic Event Handlers)等功能。 但是值得注意的是,blockdom 不是一個架構,它甚至沒有元件的概念。 blockdom 旨在成為較底層的抽象層,可以在其上抽象出其他架構。而關于如何建立自定義的架構,blockdom 提供了非常細緻的入門文檔,這一點可以在文末的參考資料中擷取。

同時,blockdom api 體積非常小,隻有 6 個建立 vnode 的函數,3 個操作 vdom 樹的函數和一個配置對象。目前 blockdom 通過 MIT 協定在 Github上開源,是一個值得了解的前端開源項目。

2.深入了解 blockdom

2.1 黃金法則

根據設計,blockdom 假設虛拟樹被相同形狀的虛拟樹修補(patched)。 當将模闆編譯成渲染函數(預期用例)時,這很自然。 但這意味着不能用另一種類型的塊(或具有不同類型節點的虛拟樹)修補塊。 為此,需要明确使用 toggler 元素。

const block1 = createBlock(`<div>block1</div>`);
const block2 = createBlock(`<div>block2</div>`);
// 錯誤:樹的形狀不一樣!!!
{
  const tree = block1();
  mount(tree, document.body);
  patch(tree, block2());
}
// 正确:樹使用toggler元素來區分兩個子樹
{
  const tree = toggler("tree1", block1());
  mount(tree, document.body);
  patch(tree, toggler("tree2", block2()));           

2.2 操縱虛拟節點 vnodes

blockdom 提供三個功能:

  • 最初調用 mount(vnode, target) 以在 target(應該是 html 元素)内插入 vnode 樹, 這将建立相關的 DOM 并将适當的引用存儲在 vnode 中。
  • patch(vnode1, vnode2) 用于用新的 vnode 樹更新(已經安裝的)vnode 樹。 此方法将 patch dom,并更新 vnode 中的内部引用。 vnode2 保持不變,可以丢棄。 另外,請注意,如果 vnode1 和 vnode2 是相同的引用,則将完全跳過 patch 過程,這是可以實作記憶的方式。
  • remove(vnode) 是一種将删除(已安裝的)vnode 樹的方法。

下面是不同的 vnode 類型:

  • block:html 元素的表示(帶有子元素/屬性)
  • multi :vnode 靜态清單的表示(可能 undefined 或不同類型)
  • list:vnode 的動态清單(具有相同的類型)
  • text:表示文本節點的簡單 vnode
  • toggler:允許在不同類型的子節點之間動态切換的容器節點
  • html:表示任意的 html 内容
  • comment:代表一個 html 注釋

可以通過下面的方式建立 block:

const block = createBlock(`<div>hello blockdom</div>`);           

當處理固定數量的 vnode 時,multi block 很有用。 例如,具有多個連續元素的模闆。 此外,它的部分或全部 vnode 可以是 undefined,特别适合 children 元素的條件渲染。 如果子節點是 undefined,那麼将自動使用空的 text 節點替換:

const block1 = createBlock(`<div>1</div>`);
const block2 = createBlock(`<div>2</div>`);

const tree = multi([block1(), block2()]);
// represents `<div>1</div><div>2</div>`
const otherTree = multi([block1(), undefined]);
// represents `<div>1</div>`           

comment 節點的使用則非常簡單:

// represents 3 text nodes: blackyellowred
const tree = comment("some text");
// will be rendered as: "<!--some text-->"           

更多節點類型的使用可以檢視文末的參考資料,這裡不再繼續展開。

擴充 blockdom

blockdom 旨在用作實際架構中的底層實作, 一些架構(例如 Owl,blockdom 正在研究的架構)将需要一種方法來添加某種元件系統。 為此,最好的方法似乎是添加新類型的 vnode。

下面是 blockdom vnode 的接口:

export interface VNode<T = any> {
  mount(parent: HTMLElement, afterNode: Node | null): void;
  moveBefore(other: T | null, afterNode: Node | null): void;
  patch(other: T, withBeforeRemove: boolean): void;
  beforeRemove(): void;
  remove(): void;
  firstNode(): Node | undefined;
}           

是以,要添加一個新的 vnode 類型,隻需要用這些方法定義一個對象或一個類,它将與 mount/patch/remove 方法一起工作。

注意 beforeRemove 方法:它是一種用于讓架構知道 vnode 将被删除的方法,該方法在删除節點之前被調用。

3.blockdom 性能

3.1 blockdom 與其他架構性能對比

憑借其基于 block 的設計,blockdom 可以在内部使用 cloneNode(true) 方法在一次調用中快速建立 dom 元素,而不是多次調用。 此外,blockdom 的虛拟樹要小得多,這反過來又大大加快了 diff 過程。

javascript web 架構基準測試在一套标準化的基準測試中比較了許多架構。 下面是 blockdom 與 vanilla js 、solid(快速細粒度反應架構)和 ivi(最快的虛拟 dom 實作)相比的性能資料。

blockdom:世界上最快的虛拟 dom 庫!

請注意,這種比較并不完全公平,因為 blockdom 并不是一個真正的架構。 基于 blockdom 建構的架構将有一定的額外開銷,具體取決于功能集。 是以,在此基準測試中,基于虛拟 dom 的架構可能會比 solidjs 稍慢。

3.2 為什麼 blockdom 這麼快

blockdom 之是以快是因為一個基本的設計選擇,即用 block 樹(tree of blocks)而不是元素來表示虛拟 dom。 這意味着:

  • 可以使用 cloneNode(true) 來快速建立 DOM 元素
  • 虛拟樹很小,是以 diff 快
  • 使用更少的記憶體

還有另一種設計選擇使其速度更快,即假設虛拟樹總是用相同結構的虛拟樹 patch(切換器元素除外),是以下面也是一個很重要的原因。

  • diff 過程不必在過程的每一步都檢查兩個 vnode 是否為同一類型 最後,許多較小規模的優化,例如:捕獲對 dom 操作函數的引用,而不是每次都查找。

4.使用 blockdom

4.1 安裝 blockdom

可以通過如下 npm 方式進行安裝:

npm i blockdom
yarn add blockdom           

當然也可以通過靜态 CDN 的方式直接引入:

https://unpkg.com/blockdom@{VERSION}/dist/blockdom.iife.min.js

// for the latest version
https://unpkg.com/blockdom/dist/blockdom.iife.min.js           

4.2 blockdom 示例

blockdom 允許開發者可以使用更大的 dom 單元在 blockdom 中工作,而不是像 h('div', {}, [...some children]) 那樣,比如下面的例子:

// 建立一個block類型
const block = createBlock(
  `<div class="some-class"><p>hello</p><blockdom-child-0/></div>`
);
const subBlock = createBlock(`<span>some value: <blockdom-text-0/></span>`);

// 建立一個 blockdom 虛拟樹
const tree = block([], [subBlock(["blockdom"])]);

// 挂載虛拟樹
mount(tree, document.body);

// 輸出結果:
// <div class="some-class"><p>hello</p><span>some value: blockdom</span></div>           

上面的示例顯示了 mount 功能。下面是一個更有趣的例子,其是一個動态的計數器清單,具有處理程式、清單和動态内容:

const counterBlock = createBlock(`
    <div class="counter">
        <button block-handler-1="click">Increment</button>
        <span>Value: <block-text-0/></span>
    </div>`);

const mainBlock = createBlock(`
    <div>
        <div><button block-handler-0="click">Add a counter</button></div>
        <div><block-child-0/></div>
    </div>`);

const state = [{ id: 0, value: 3 }];

function addCounter() {
  state.push({ value: 0, id: state.length });
  update();
}

function incrementCounter(id) {
  const counter = state.find((c) => c.id === id);
  counter.value++;
  update();
}

function render(state) {
  const counters = state.map((c) => {
    const handler = [incrementCounter, c.id];
    return withKey(counterBlock([c.value, handler]), c.id);
  });
  return mainBlock([addCounter], [list(counters)]);
}

let tree = render(state);
mount(tree, document.body);

function update() {
  patch(tree, render(state));
}           

需要注意的是,block 類型需要首先建立,其具有特殊屬性或标記,例如 <block-text-0 /> 或 block-handler-1="click"。

blockdom 然後處理 block 模闆,找到所有特殊标簽、屬性并生成将建立和更新這些值的快速函數(fast functions),該數字對應于建構 block 時給出的資料的索引。

此外,blockdom 支援合成處理程式(意思是它隻在 body 上設定一個實際的事件處理程式,這是一種優化)。 要使用此功能,隻需使用 .synthetic 字尾即可:

const counterBlock = createBlock(
  `<button block-handler-1="click.synthetic">Increment</button>`
);           

也可以在捕獲模式下設定處理程式:

const counterBlock = createBlock(
  `<button block-handler-1="click.capture">Increment</button>`
);           

5.本文總結

本文主要和大家介紹下 blockdom,即号稱是世界上最快的虛拟 dom 庫。相信通過本文的閱讀,大家對 blockdom 都會有一個初步的了解。

因為篇幅有限,文章并沒有過多展開,如果有興趣,可以在我的首頁繼續閱讀,同時文末的參考資料提供了大量優秀文檔以供學習。最後,歡迎大家點贊、評論、轉發、收藏!

參考資料

https://github.com/ged-odoo/blockdom

https://github.com/ged-odoo/blockdom/blob/main/doc/reference.md#the-golden-rule

https://github.com/ged-odoo/blockdom/blob/main/doc/extending_blockdom.md

https://github.com/ged-odoo/blockdom/blob/main/doc/performance_notes.md

https://github.com/ged-odoo/blockdom/blob/main/doc/make_your_own_framework/readme.md

https://devpress.csdn.net/react/62ec6af219c509286f416f8d.html

繼續閱讀