Vue 仮想 DOM クイックスタート

Vue 仮想 DOM クイックスタート

仮想DOM

仮想DOMとは何か

DOM は、ドキュメントをノード ツリーの形式で表現するドキュメント オブジェクト モデルです。

仮想 DOM は実際の DOM ではありません。むしろ、それは JavaScript オブジェクトです。

通常の DOM ノードは HTML では次のように表されます。

<div クラス = 'testId'>
    <p>こんにちは</p>
    <p>ようこそ</p>
</div>

仮想 DOM では次のようになります。

{
    タグ: 'div',
    属性:
        クラス: ['testId']
    },
    子供たち:[
        // p 要素 // p 要素]
}

理解しやすいように、仮想 DOM を仮想 + DOM の 2 つの部分に分割できます。

  • 仮想: 仮想 DOM は実際の DOM ではなく、JavaScript オブジェクトであることを意味します。
  • dom: 仮想 dom がドキュメントをノード ツリーのような形式で表現できることを示します。

仮想DOMの役割

現在主流となっているフレームワークはすべて、宣言型 DOM 操作用のフレームワークです。状態と DOM 間のマッピング関係を記述するだけで済みます。フレームワークは、状態をビュー (実際の DOM) に変換するのに役立ちます。

最も単純なアプローチは、状態をビューにレンダリングし、状態が更新されるたびにビュー全体を更新することです。

このアプローチのパフォーマンスは想像できます。より良いアイデアは、状態が変化すると、その状態に関連する DOM ノードのみが更新されることです。仮想 DOM はこのアイデアを実装する 1 つの方法にすぎません。

具体的な手順:

  • 状態 -> 実ドメイン(初期)
  • 状態 -> 仮想 DOM -> 実際の DOM (仮想 DOM を使用)

状態が変化すると、新しい仮想 DOM が生成され、以前のものと現在のものを比較して更新が必要な部分を見つけ、実際の DOM が更新されます。

Vue の仮想 DOM

実際の DOM はノード (Node) で構成され、仮想 DOM は仮想ノード (vNode) で構成されます。

Vue では、仮想 DOM は主に次の 2 つのことを行います。

  • 実ノード(Node)に対応する仮想ノード(vNode)を提供する
  • 新しい仮想ノードと古い仮想ノードを比較し、違いを見つけてビューを更新します。

「仮想 DOM」は、Vue コンポーネント ツリーによって構築された VNode ツリー全体の名前です - vue 公式サイト

vノード

vNodeとは

前述の通り、vNode(仮想ノード)は実ノード(Node)に相当します。

vNode はノード記述オブジェクトとして理解できます。実際のDOMノードを作成する方法を説明します

vue.js には vNode クラスがあります。さまざまなタイプの vNode インスタンスを作成するために使用できます。さまざまなタイプの vNode は、さまざまなタイプの DOM 要素に対応します。コードは次のとおりです。

デフォルトクラスVNodeをエクスポートする{
   コンストラクタ(
    タグ?: 文字列、
    データ?: VNodeData、
    子?: ?Array<VNode>,
    テキスト?: 文字列、
    elm?: ノード、
    コンテキスト?: コンポーネント、
    コンポーネントオプション?: VNodeComponentOptions、
    asyncFactory?: 関数
  ){
    this.tag = タグ
    this.data = データ
    this.children = 子供
    this.text = テキスト
    this.elm = エルム
    this.ns = 未定義
    this.context = コンテキスト
    this.fnContext = 未定義
    this.fnOptions = 未定義
    this.fnScopeId = 未定義
    this.key = データ && データ.key
    this.componentOptions = コンポーネントオプション
    this.componentInstance = 未定義
    this.parent = 未定義
    this.raw = 偽
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = 非同期ファクトリー
    this.asyncMeta = 未定義
    this.isAsyncPlaceholder = false
  }

  子を取得():コンポーネント | void {
    this.componentInstance を返す
  }
}

vNode クラスによって作成されたインスタンスが本質的に通常の JavaScript オブジェクトであることは、コードから簡単にわかります。

vNode の種類

vNode クラスを通じてさまざまな種類の vNode を作成できることはすでに紹介しました。異なるタイプの vNode は、有効な属性によって区別されます。たとえば、isComment = true はコメント ノードを示し、isCloned = true はクローン ノードを示します。

vNode タイプには、コメント ノード、テキスト ノード、クローン ノード、要素ノード、コンポーネント ノードが含まれます。

コメント ノード、テキスト ノード、クローン ノードのコードは次のとおりです。

/*
コメント ノードの有効な属性: {isComment: true、テキスト: 'コメント ノード'}
*/
エクスポートconst createEmptyVNode = (テキスト: 文字列 = '') => {
  定数ノード = 新しい VNode()
  node.text = テキスト
  // コメント node.isComment = true
  戻りノード
}
/*
テキスト ノードの有効な属性: {text: 'text node'}
*/
エクスポート関数createTextVNode (値: 文字列 | 数値) {
  新しい VNode(undefined, undefined, undefined, String(val)) を返します
}

// 最適化された浅いクローン
// 静的ノードとスロットノードに使用されます。
// 静的ノードとスロットノードの場合 // 複数のレンダリングでは、それらを複製することで、DOM操作が
// elm 参照に基づきます。
// クローンノードエクスポート関数 cloneVNode (vnode: VNode): VNode {
  const クローン = 新しい VNode(
    vnode.タグ、
    vnode.data、
    //#7975
    // 複製の際に元の配列が変更されないように、子配列を複製します
    // 子供。
    vnode.children && vnode.children.slice(),
    vnode.text、
    vnode.elm、
    vnode.コンテキスト、
    vnode.componentOptions、
    vnode.asyncファクトリ
  )
  クローン.ns = vnode.ns
  クローンされた.isStatic = vnode.isStatic
  クローンされたキー = vnode.key
  クローンされた.isComment = vnode.isComment
  クローンされたfnContext = vnode.fnContext
  クローンされたfnOptions = vnode.fnOptions
  クローンされたfnScopeId = vnode.fnScopeId
  クローンされたasyncMeta = vnode.asyncMeta
  // ノードをクローンとしてマークします。isCloned = true
  クローンを返す
}

ノードを複製すると、実際には既存のノードのすべてのプロパティが新しいノードに割り当てられ、最終的にcloned.isCloned = trueで複製されたノードとしてマークされます。

要素ノードには通常、次の 4 つの属性があります。

  • タグ: ノード名。たとえば、div、p
  • data: ノード上のデータ。たとえば、クラス、スタイル
  • children: 子ノード
  • コンテキスト: コンポーネント内でレンダリングされる

コンポーネント ノードは要素ノードに似ており、次の 2 つの固有のプロパティが含まれます。

  • componentOptions: propsData、リスナー、子、タグなどのコンポーネント ノードのオプション パラメータ
  • componentInstance: コンポーネントのインスタンス

パッチ

Vue で仮想 DOM が行う最初のことはすでに紹介しました。実際のノード (Node) に対応する仮想ノード (vNode) を提供することです。次に、2 番目のことを紹介します。新しい仮想ノードを古い仮想ノードと比較し、違いを見つけて、ビューを更新します。

Vue で実装されている 2 つ目のものは、パッチ適用または修復を意味する patch と呼ばれます。古い vNode と新しい vNode を比較し、違いを見つけて、既存の DOM に基づいてパッチを適用することで、ビューが更新されます。

vNode を比較して違いを見つけることが手段であり、ビューを更新することが目的です。

ビューを更新するということは、ノードの追加、ノードの削除、ノードの更新を行うということに他なりません。次に、ノードをいつ追加するか、どこに追加するか、ノードをいつ削除するか、どのノードを削除するか、ノードをいつ更新するか、どのノードを更新するかを 1 つずつ分析します。

注意: vNode と oldVNode が異なる場合は、vNode が優先されます。

新しいノードの追加

1 つの状況は、vNode は存在するが oldVNode が存在しない場合、新しいノードを追加する必要があることです。最も典型的なのは、odlVNode が存在しないための初期レンダリングです。

もう 1 つのケースは、vNode と oldVNode がまったく同じノードではない場合です。このとき、vNode を使用して実際の DOM ノードを生成し、oldVNode が指す実際の DOM ノードの隣に挿入する必要があります。 oldVNode は放棄されたノードです。たとえば、次のような状況です。

<div>
  <p v-if="type === 'A'">
    私はノードAです
  </p>
  <span v-else-if="type === 'B'">
    私はAとは全く異なるノードBです
  </span>
</div>

タイプが A から B に変更されると、ノードは p から span に変更されます。vNode と oldVNode はまったく同じノードではないため、新しいノードを追加する必要があります。

ノードの削除

ノードが oldVNode にのみ存在する場合は、それを削除します。

ノードの更新

前のセクションでは、新しいノードの追加とノードの削除のシナリオを紹介しました。これらには共通点が 1 つあります。vNode は oldVNode とはまったく異なるということです。

しかし、より一般的なシナリオは、vNode と oldVNode が同じノードである場合です。次に、vNode と oldVNode 間のより詳細な比較を行い、oldVNode に対応する実際のノードを更新する必要があります。

テキストノードの場合、ロジックは当然単純です。まず、古い vNode と新しい vNode を比較して、同じノードであることを確認します。次に、oldVNode に対応する dom ノードのテキストを vNode のテキストに変更します。しかし、インターフェース内のツリー コンポーネントなどの複雑な vNode の場合、このプロセスは複雑になります。

新しいノード - ソースコード分析

考えてみてください。前述したように、vNode の種類は、コメント ノード、テキスト ノード、クローン ノード、要素ノード、コンポーネント ノードです。これらすべての型が作成され、DOM に挿入されますか?

回答: コメント ノード、テキスト ノード、要素ノードのみ。 HTML はこれらのタイプのみを認識するためです。

ノードは上記の3種類しかないので、種類に応じて対応するノードを作成し、対応する位置に挿入します。

要素ノードを例にとると、vNode に tag 属性がある場合、それは要素ノードであることを意味します。 createElement メソッドを呼び出して対応するノードを作成し、次に appendChild メソッドを使用してそれを指定された親ノードに挿入します。親要素がすでに表示されている場合は、その下に要素を挿入すると自動的にレンダリングされます。vNode の isComment プロパティが true の場合は、コメント ノードを示します。どちらも true でない場合は、テキスト ノードを示します。

通常、要素には子ノードが存在するため、ここでは再帰的なプロセスが実行されます。つまり、vNode 内の子ノードを 1 つずつ走査し、ノードを作成してから、それらを親ノード (親ノードは、作成したばかりの dom ノード) に、レイヤーごとに再帰的に挿入します。

ソースコードをご覧ください:

// 要素を作成する function createElm (
  vノード、
  挿入されたVnodeQueue、
  親エルム、
  refElm、
  ネストされた、
  所有者配列、
  索引
){
  if (isDef(vnode.elm) && isDef(ownerArray)) {
    // この vnode は以前のレンダリングで使用されました。
    // 今は新しいノードとして使用されているので、elmを上書きすると
    // 挿入として使用した場合、将来的にパッチエラーが発生する可能性があります
    // 参照ノード。代わりに、作成する前にオンデマンドでノードを複製します。
    // それに関連付けられた DOM 要素。
    vnode = ownerArray[インデックス] = cloneVNode(vnode);
  }

  vnode.isRootInsert = !nested; // 遷移のチェックに入る
  コンポーネントを作成する場合(vnode、挿入されたVnodeQueue、親Elm、refElm) {
    戻る
  }

  var データ = vnode.data;
  var children = vnode.children;
  var タグ = vnode.tag;
  // タグ属性を持ち、要素ノードであることを示す if (isDef(tag)) {
    vnode.elm = vnode.ns
      ? nodeOps.createElementNS(vnode.ns、タグ)
      // 要素を作成します。 nodeOps はクロスプラットフォームを伴います: nodeOps.createElement(tag, vnode);
    スコープを設定します(vnode);

    /* イスタンブールは無視します */
    {
      // 子ノードを再帰的に作成し、親ノードに挿入します。createChildren(vnode, children, insertedVnodeQueue);
      if (isDef(データ)) {
        フックの作成を呼び出します(vnode、挿入されたVnodeQueue);
      }
      //vnode に対応する要素を親要素に挿入します insert(parentElm, vnode.elm, refElm);
    }

  // isCommentプロパティはコメントノードを示します} else if (isTrue(vnode.isComment)) {
    vnode.elm = nodeOps.createComment(vnode.text);
    //親ノードを挿入insert(parentElm, vnode.elm, refElm);
  // それ以外の場合は子ノードです} else {
    vnode.elm は nodeOps.createTextNode(vnode.text);
    //親ノードを挿入insert(parentElm, vnode.elm, refElm);
  }
}

// 子ノードを再帰的に作成し、親ノードに挿入します。 vnodeは親ノードを表します。関数createChildren(vnode, children, insertedVnodeQueue){
  Array.isArray(children) の場合
    process.env.NODE_ENV !== 'production' の場合 {
      checkDuplicateKeys(子);
    }
    // 子ノードを一つずつ作成し、親ノードに挿入します for (var i = 0; i < children.length; ++i) {
      createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
    }
  } そうでない場合 (isPrimitive(vnode.text)) {
    nodeOps.appendChild(vnode.elm、nodeOps.createTextNode(String(vnode.text)));
  }
}

ノードの削除 - ソースコード分析

ノードの削除は非常に簡単です。ソースコードを直接見てみましょう:

// 指定されたノードのセットを削除する function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
  (; startIdx <= endIdx; ++startIdx) の場合 {
    var ch = vnodes[startIdx];
    if (isDef(ch)) {
      if (isDef(ch.tag)) {
        削除フックを削除して呼び出します(ch);
        DestroyHook を呼び出します(ch);
      } else { // テキストノード
        // ノードを削除しますremoveNode(ch.elm);
      }
    }
  }
}

// 単一のノードを削除する function removeNode (el) {
  var 親 = nodeOps.parentNode(el);
  // 要素は v-html / v-text によりすでに削除されている可能性があります
  if (isDef(親)) {
    // nodeOps はクロスプラットフォーム メソッド nodeOps.removeChild(parent, el); をカプセル化します。
  }
}

以上がVue Virtual DOMクイックスタートの詳しい内容です。Vue Virtual DOMの詳細については、123WORDPRESS.COMの他の関連記事もご覧ください。

以下もご興味があるかもしれません:
  • この記事は、Vueの仮想Domとdiffアルゴリズムを理解するのに役立ちます。
  • Vue仮想DOMの原理
  • Vue 仮想 DOM の問題について
  • Vueソースコード解析における仮想DOMの詳しい説明
  • Vueにおける仮想DOMの理解のまとめ
  • Vue 仮想 Dom から実際の Dom への変換
  • Vue の仮想 DOM に関する知識ポイントのまとめ

<<:  MySQL の FIND_IN_SET() と IN の違いを簡単に分析します

>>:  CentOS に Memcached と PHP Memcached 拡張機能をインストールする

推薦する

Docker環境でJenkinsを設定すると、タスクをビルドするときにコンソールログに文字化けした中国語の文字が表示されます

目次1. 問題の説明: 2. Jenkins設定のトラブルシューティング3. コードログのエンコード...

Vue.js で AntV X6 を使用する手順の例

目次0x0 はじめに0x1 インストール0x2 ノードサイドバー0x3 統合例0x0 はじめにプロジ...

MySQL 8.0 の降順インデックス

序文インデックスが順序付けられていることは誰もが知っていると思いますが、MySQL の以前のバージョ...

中国語フォントの英語名まとめ

CSS の font-family プロパティを使用して中国語フォントを参照する場合、フォントを定義...

Linux系でよく使われる運用・保守コマンド(まとめ)

目次1. システム監視2. ファイル操作3. ネットワーク通信4. システム管理仕事で必要なLinu...

フォーム要素とプロンプトテキストが揃っていない問題

最近のプロジェクトでは、多くのフォーム、特にチェックボックスとラジオボタンの作成が含まれます。しかし...

JavaScript のディープコピーの落とし穴

序文以前、ある会社の面接に行ったとき、面接官から「オブジェクトを深くコピーするにはどうすればよいです...

JS を使用して航空機戦争の小さなゲームを実装する

この記事の例では、参考のために航空機戦争ゲームを実装するためのJSの具体的なコードを共有しています。...

MySQLがOracleのnvlと同様の機能を持つことができるかどうかについての簡単な議論

isnullの代わりにifnullを使用するisnull は、null かどうかを判断するために使用...

純粋な CSS3 マインドマップ スタイルの例

マインドマップ彼はおそらく次のように見えるでしょう: インターネット上の実装のほとんどは d3.js...

JavaScript におけるさまざまなバイナリオブジェクトの関係の詳細な説明

目次序文さまざまなオブジェクト間の関係配列バッファ型付き配列Uint8ClampedArray文字間...

CentOS8 システムをベースにした Gitlab を構築するために Docker を使用する詳細なチュートリアル

目次1. Dockerをインストールする2. GitLabをインストールする3. GitLabを初期...

JavaScript の基本演算子

目次1. オペレーター要約する1. オペレーター演算子は、代入、比較、算術演算などの機能を実装するた...

よく使用される MySQL 関数の完全なリスト (分類および要約)

1. 数学関数ABS(x) xの絶対値を返します。 BIN(x) xの2進値を返します(OCTは8...

サーバー間のファイル バックアップ ソリューション、サーバー ファイルを別のサーバーに自動的にバックアップする方法は?

多くの組織ではファイル サーバーをバックアップする必要があり、あるサーバーから別のファイル サーバー...