Open13

仮想DOMの中身を理解する

Yug (やぐ)Yug (やぐ)

うーん?なんか実DOMと結構違うな

仮想DOMでは以下のようなオブジェクトに変換されて表現されています。

{
  name: "div",
  props: {id: "app"},
  nodeType: null,
  key: null,
  realNode: ... //実際のDOMへの参照
  children: [
    {
      name: "h1", 
      props: {}, 
      nodeType: null,
      key: null,
      realNode: ... //実際のDOMへの参照
      children: [
       {
         name: "Hello World",
         props: {},
         nodeType: TEXT_NODE,
         key: null,
         realNode: ... //実際のDOMへの参照
         children: []
        }
      ]
    }
  ]
}
Yug (やぐ)Yug (やぐ)

実DOMでいうインタフェースに似てるな。あれがそのままオブジェクトになったみたいな。

実DOMでいうNodeオブジェクトって、インタフェースの具現化としての実体で、HTML要素そのものの見た目だったけど、仮想DOMでいうNode(VNode)オブジェクトはインタフェースがそのままの見た目でオブジェクトとして具現化したみたいな感じなのか

例として1つのdiv要素で比較

  • 実DOMでいうNodeオブジェクト
<div id="example">aaa</div>
  • 仮想DOMでいうVNodeオブジェクト
{
  name: "div",
  props: {id: "example"},
  nodeType: null,
  key: null,
  realNode: ... //実際のDOMへの参照
  children: [...]
}
Yug (やぐ)Yug (やぐ)

いや、でも実DOMのNodeオブジェクトはconsole.logで出したからそう見えるだけであって、console.dirでDOM要素として見るとこんな感じなので、実質同じと考えていいかも

この大量のプロパティは、このNodeオブジェクトの実装元であるHTMLDivElementやHTMLElementなどから引き継いだもの。

Yug (やぐ)Yug (やぐ)

なのでどちらも結構似てて、インターフェース的な見た目で、プロップスやメソッドが定義されてる感じと捉えて良いか。プロパティはそれぞれ違うにしろ。

変な勘違いしかけたけどとりあえずOK

Yug (やぐ)Yug (やぐ)

大事:
要素のテキスト部分(ここでいうHello world)はテキストノードであり、子ノードである。
つまりh1ノードの兄弟ノードではなく子ノード。これはDOMも仮想DOMも同じ。

<h1>Hello world</h1>
Yug (やぐ)Yug (やぐ)

これは実DOMと同じ

VNodeが階層構造になって一個のウェブページを表せるようになったのが仮想DOMです。

Yug (やぐ)Yug (やぐ)

これも同じ。名前がtextNodeなのも同じ

更に仮想DOMではただの文字列もVNodeとして表現します。例えば下のようなものです。

{
    name: "Hello World",
    props: {},
    nodeType: TEXT_NODE,
    key: null,
    realNode: ... //実際のDOMへの参照
    children: []
}
Yug (やぐ)Yug (やぐ)

んん?どゆこと?

実際にReactなどが要素を更新する時は、前の仮想DOMを表現したオブジェクトと新しい仮想を表現したDOMオブジェクトを比較して違いがあったところだけ更新

↓こうでは?

実際にReactなどが要素を更新する時は、前の実DOMを表現したオブジェクト(=仮想DOM①)と、新しい実DOMを表現したオブジェクト(=仮想DOM②)を比較して違いがあったところだけ更新

「前の仮想DOMを表現したオブジェクト」というのがよくわからない。
仮想DOMを作ろうとしてる段階で前の仮想DOMを参考にするって変では?

前回の仮想DOM②を、今回の(最新の)仮想DOM①とする、みたいな意味?なら理解できるが

Yug (やぐ)Yug (やぐ)

https://zenn.dev/ak/articles/00616eb99523c2
これ短いので全部読んでみたけどやっぱ仮想DOM①は実DOMをコピーしているっぽい。

「DOMのコピー」である仮想DOMを作成(仮想DOM 1)

んで仮想DOM②の方は

コンポーネントの状態が変更されると、新しい仮想DOMが作り出される(仮想DOM 2)

とのことなので、つまりレンダーの最後にsetterの更新処理が一気に走る瞬間にやっと作られるのだろう。

ただ、どのように作られるんだろう?が気になる。

実DOMをコピーするのか仮想DOM①をコピーするのか、多分この2択だと思うんだけど、どっちなのかは気になるところ

とりあえず読み進めていく

Yug (やぐ)Yug (やぐ)

1つのHTML要素に1つのVNodeが対応しているイメージで

だがVNodeはそれに加えて子ノードの情報も持ってるぽい。親は無いのか
まずこの各プロパティが何なのか理解することでVNodeの解像度を上げようという感じ

interface VirtualNodeType {
  name: HTMLElementTagNameMap | string; // divやh1等の場合はHTMLElementTagNameMap、文字を表すVNodeの場合はstring型
  props: ...; // HTML要素の属性
  children: VirtualNodeType[];
  realNode: ...; // 実際のDOMへの参照
  nodeType: ...; // このVNodeのタイプ(文字を表すノードなのか要素を表すノードなのか)
  key: ...; // keyを表す
}
Yug (やぐ)Yug (やぐ)

んーでも厳密に言うとちがうのかも?

つまりVNodeはHTML要素に対応してるというより、HTML要素に対応しているDOM Nodeに対応しているのではないだろうか、その方が直感的ではある

と思いきやVNodeにあるpropsというプロパティはこんな感じでHTML要素の属性が入ってるらしい

props: {
    id: "app",
    class: "example"
},

あーでも属性はDOMでも保持してるか。
例えばid属性は普通にidという名前で保持されてたりするし、

class属性もclassNameという名前で保持されてる

じゃあますますどっちか分からんぞ

Yug (やぐ)Yug (やぐ)

あーでもkey属性という、HTML要素にもDOM Nodeにも無い属性があるのかぁ

// propにはkeyとoninputやclass、id等のHTMLElementの属性の名前が入ります
interface DOMAttributes {
  key?: KeyAttribute;
  [prop: string] : any;
}

じゃあ普通に
「HTML要素にVNodeは対応してるよ、ただkeyっていう独自プロパティだけ追加してるよ」
て感じでイメージして良いかも