Create a gist now

Instantly share code, notes, and snippets.

Incremental DOM ざっと見たやつ

Incremental DOM

Introducing Incremental DOM — Google Developers — Medium

Reactやvirtual-dom、Glimmer(Ember)などVirtual DOMの実装は色々あるが、これらのVirtual DOM実装には2つの問題がある

  • 既存のテンプレート言語を利用していない(しにくい)
  • モバイルでのパフォーマンス、特にメモリに関しては大きすぎる

これらを解決するためにIncremental DOMと言うものを作っている(WIP)

Incremental DOMの特徴としては

  • mustache.jsのようにテキストからどういうHTMLになるかがわかるようなデザイン
  • 再描画する際のメモリのフットプリントを小さくできる(Virtual DOMは新しいvDOM Treeを作って比較するため、再描画するたびに大きなオブジェクトを作るということになる)

Reducing memory usage

既存のVirtual DOM実装は大きく分けて2つのフェーズを行っている

  1. レンダリングするための新しいVirtual DOM treeを作る
  2. 作ったVirtual DOM Treeを前回のVirtual DOM Treeと比較して、実際のDOM Treeへ反映していく

この手法だと、変更点があると保持しておくVirtual DOM Treeが大きくなってメモリにはやさしくない

Incremental DOMの手法

Incremental DOMはVirtual DOMのモデルを1つのフェーズに変更して行う

  1. 新しい(Virtual) DOM Treeを作りながら、(実際の) DOM Treeをwalkしつつ変更していく。 (変更がなければ何も作らないし、変更があってもその最小限のVirtul DOM Treeを作ってdiff/patchする)

これを行うために、Incremental DOMでは実際のDOM Nodeに対してメタ情報を埋め込んでいく。 実際のDOM Treeにアクセスするわけだが、これは十分に早い。

Incremental DOMのテンプレート

Virtual DOM実装は一般的にタグは閉じてるものになってる。 (単純に言えばdocument.createElement()にあたるAPIしかなく、 Virtual DOMのNodeを作る場合に必ずタグが閉じた状態になる)

一般的なテンプレートエンジンは以下の<section>のように閉じられてなくても動作する

{template chunk1}
<h1>Hello World</h1>
<section id="body"> /* Content comes after this */
{/template}

これをIncremental DOMでもサポートしたかったので、 OpenとCloseのように組み合わせとなってる。

 	

data.items.forEach(function(item, index) {
  elementOpen('x-item', index);
  text('item' + index);
  elementClose('x-item');
});

つまり、既存のテンプレート言語をそのままIncremental DOM上で再現できるはずという話

詳しい例は

FAQ

  • Virtual DOMより早いの?
    • 一般解はまだない。メモリとGCには優しい
  • shouldComponentUpdateみたいなメカニズムは?
    • 入れたい。メモリ的にちょっとむずかしいかもしれないけど
  • Productionで使った?
    • No
  • 実際に使った?
    • Closure Templates
  • 他のVirtual DOM実装と連携できる?
    • Incremental DOMは零レイヤーな実装
    • このうえにVirtual DOM APIをhigh level apiとしておけるかも
  • サイズ
    • 2.6KB(gzip)
  • WIPなのでIncremental DOMへContribution歓迎

コードリーディング(ざつ)

@ google/incremental-dom at c4b02e5aac4da34c85d0b8f7eb3df8f44d0987f3

alignWithDOM https://github.com/google/incremental-dom/blob/c4b02e5aac4da34c85d0b8f7eb3df8f44d0987f3/src/alignment.js#L53-53

  • DOM Tree Walkerでtraverseしていく
  • それぞれNodeを訪ねてメタデータがあるかを見る
    • ある: 既存のNode(Virtual DOM的には前回のVirtual DOM)
    • そのメタデータからNodeを取り出す
    • ない: メタデータをそのNodeから作って保存
    • 変更後のNodeがとれたので訪ねたNodeとすり替える(メタデータを入れる)
  • 訪問フラグ

メタデータ

elementOpen: https://github.com/google/incremental-dom/blob/c4b02e5aac4da34c85d0b8f7eb3df8f44d0987f3/src/virtual_elements.js#L185-185

  • alignWithDOM()でNodeを取り出す
  • メタデータをアップデート
  • firstChildをたどって下へ

おおまかな流れ

  elementOpenStart('div', '', []);
    if (obj.key) {
      attr('data-expanded', obj.key);
    }
  // ここまででargsを貯める
  elementOpenEnd();// ここで実際のNodeへ適応
  elementClose('div');
  • open が enter
  • closeが leave

感じでDOM Treeを走査していく流れ

<div>
<p>
</p>
</div>
<span></span>

というDOM Treeだと

openではdiv -> p とたどって、 closeは p -> div -> となりの要素へ(span)

elementOpen('div', null, null,
    'style', {
      color: 'white',
      backgroundColor: 'red'
    });
  …
elementClose('div');

みたいな要素を組み立てるものを 一時的なバッファとしてargsBuilderにいれている。

このバッファは、 open->close 次 open->closeとした時に同じ変数を使いまわしてる。 (tag名やattrなどの中身を置き換える)

なので、全部のTreeを走査するときに必要なメモリは 一つのopenの間にある小さなTreeの分だけで良い。

Virtual DOMの場合は、こういう情報を全体のTreeとしてメモリとかに持ってる。

See also

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment