Vue命令の実装原理の分析

Vue命令の実装原理の分析

1. 基本的な使い方

公式サイトの場合:

<div id='アプリ'>
  <入力タイプ="テキスト" v-model="入力値" v-focus>
</div>
<スクリプト>
  Vue.directive('focus', {
    // 要素を初めてバインドするときにbind()を呼び出す {
      コンソールログ('バインド')
    },
    // バインドされた要素が DOM に挿入されると...
    挿入: 関数 (el) {
      console.log('挿入されました')
      el.フォーカス()
    },
    // コンポーネント VNode が更新されたら update() を呼び出す {
      コンソールログ('更新')
    },
    // 命令が配置されているコンポーネントのすべての VNode とその子 VNode が更新された後に、componentUpdated() を呼び出します {
      console.log('コンポーネントが更新されました')
    },
    // 一度だけ呼び出される unbind() は、命令が要素からアンバインドされたときに呼び出されます {
      console.log('アンバインド')
    }
  })
  新しいVue({
    データ: {
      入力値: ''
    }
  }).$mount('#app')
</スクリプト>

2. 指示の動作原理

2.1. 初期化

グローバル API を初期化するときに、platforms/web の下で createPatchFunction を呼び出して、VNode を実際の DOM に変換するパッチ メソッドを生成します。初期化の重要なステップは、DOM ノードに対応するフック メソッドを定義することです。DOM の作成 (create)、アクティブ化 (avtivate)、更新 (update)、削除 (remove)、および破棄 (destroy) 中に、対応するフック メソッドがそれぞれポーリングされ、呼び出されます。これらのフックの一部は、命令宣言サイクルへの入り口です。

// src/core/vdom/patch.js
const フック = ['create'、'activate'、'update'、'remove'、'destroy']
エクスポート関数createPatchFunction(バックエンド){
  i,jとします
  定数 cbs = {}

  const { モジュール、 nodeOps } = バックエンド
  (i = 0; i < hooks.length; ++i) の場合 {
    cbs[フック[i]] = []
    // モジュールは、class、style、domListener、domProps、attrs、directive、ref、transition などの Vue のモジュールに対応します。
    (j = 0; j < モジュールの長さ; ++j) {
      if (isDef(モジュール[j][フック[i]])) {
        // 最後にフックを{hookEvent: [cb1, cb2 ...], ...}形式に変換します。cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
  // ....
  関数 patch (oldVnode, vnode, hydrating, removeOnly) を返す {
    // ...
  }
}

2.2 テンプレートのコンパイル

テンプレートのコンパイルは、命令パラメータを解析することです。具体的に分解された ASTElement は次のとおりです。

{
  タグ: '入力',
  親: ASTElement、
  ディレクティブ: [
    {
      arg: null、// パラメータ終了: 56、// 命令の終了文字位置 isDynamicArg: false、// 動的パラメータ、v-xxx[dynamicParams]='xxx' フォーム呼び出し修飾子: undefined、// 命令修飾子名: "model"、
      rawName: "v-model", // 命令名開始: 36, // 命令開始文字位置値: "inputValue" // テンプレート },
    {
      引数: null、
      終了: 67,
      isDynamicArg: false、
      修飾子: 未定義、
      名前: "フォーカス"、
      生の名前: "v-focus",
      開始: 57,
      価値: ""
    }
  ]、
  // ...
}

2.3. レンダリング方法の生成

Vue では、DOM を操作するために命令を使用することを推奨しています。カスタム命令は DOM または属性を変更する可能性があるため、テンプレート解析に対する命令の影響を避けてください。レンダリング メソッドを生成するときに最初に処理するのは、v-model などの命令です。これは基本的に構文糖です。レンダリング関数をスプライシングすると、値属性と入力イベントが要素に追加されます (入力を例にとると、これもユーザーがカスタマイズできます)。

(これ){
    _c('div', { を返す
        属性: {
            「id」: 「アプリ」
        }
    }, [_c('入力', {
        ディレクティブ: [{
            名前: "モデル",
            生の名前: "v-model",
            値: (入力値)
            式: "inputValue"
        }, {
            名前: "フォーカス"、
            生の名前: "v-focus"
        }],
        属性: {
            "タイプ": "テキスト"
        },
        domProps: {
            "value": (inputValue) // v-model命令を処理するときに追加される属性},
        の上: {
            "input": function($event) { // v-model ディレクティブを処理するときにカスタムイベントが追加されます if ($event.target.composing)
                    戻る;
                入力値 = $event.target.value
            }
        }
    })])
}

2.4. VNodeを生成する

vue の命令の設計は、DOM の操作を容易にすることを目的としています。VNode を生成するとき、命令は追加の処理を実行しません。

2.5. 実際のDOMを生成する

vue の初期化プロセスでは、次の 2 つの点を覚えておく必要があります。

  • 状態の初期化は親→子、例えばbeforeCreate、created、beforeMountなどであり、呼び出し順序は親→子である。
  • 実DOMのマウント順序は、子→親のようにマウントされます。これは、実DOMの生成過程でコンポーネントに遭遇した場合、コンポーネント作成プロセスが続行されるためです。実DOMの生成は、子から親へとレベルごとに接合されます。

パッチ処理中、実際の DOM を生成するために createElm が呼び出されるたびに、現在の VNode にデータ属性があるかどうかを検出します。データ属性がある場合は、invokeCreateHooks が呼び出され、最初に作成されたフック関数が実行されます。コア コードは次のとおりです。

// src/core/vdom/patch.js
関数createElm(
    vノード、
    挿入されたVnodeQueue、
    親エルム、
    refElm、
    ネストされた、
    所有者配列、
    索引
  ){
    // ...
    // createComponent には戻り値があり、これはコンポーネントを作成するメソッドです。戻り値がない場合は、次のメソッドに進みます if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      戻る
    }

    定数データ = vnode.data
    // ....
    if (isDef(データ)) {
        // 実際のノードが作成された後、命令を含むノード属性を更新します // 命令は最初に bind メソッドを呼び出し、次に命令の以降のフック メソッドを初期化します。invokeCreateHooks(vnode, insertedVnodeQueue)
    }
    // 下から上へ、insert(parentElm, vnode.elm, refElm)
    // ...
  }

上記は、ディレクティブフックメソッドの最初のエントリです。directive.js directive.js謎を解き明かすときが来ました。コアコードは次のとおりです。

// src/core/vdom/modules/directives.js

// デフォルトでは、スローされるすべてのメソッドは updateDirectives メソッドです export default {
  作成: updateDirectives、
  更新: updateDirectives、
  破棄: 関数 unbindDirectives (vnode: VNodeWithData) {
    // 破棄されると、vnode === emptyNode
    更新ディレクティブ(vnode、空ノード)
  }
}

関数 updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  if (oldVnode.data.directives || vnode.data.directives) {
    _update(古いVnode、vnode)
  }
}

関数_update(oldVnode, vnode) {
  const isCreate = oldVnode === 空のノード
  const isDestroy = vnode === 空のノード
  定数 oldDirs = normalizeDirectives(oldVnode.data.directives、oldVnode.context)
  定数 newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
  // 挿入後のコールバック const dirsWithInsert = [
  // 更新が完了した後のコールバック const dirsWithPostpatch = []

  キー、oldDir、dir を入力します。
  for (newDirsのキー) {
    oldDir = oldDirs[キー]
    dir = newDirs[キー]
    // 新しい要素の命令。挿入されたフックメソッドを 1 回実行します。if (!oldDir) {
      // 新しいディレクティブ、バインド
      callHook(dir, 'bind', vnode, oldVnode)
      dir.def が挿入されている場合
        dirsWithInsert.push(dir)
      }
    } それ以外 {
      // 既存のディレクティブを更新
      // 要素がすでに存在する場合、componentUpdatedフックメソッド dir.oldValue = oldDir.value が1回実行されます。
      dir.oldArg = 古いDir.arg
      callHook(dir, 'update', vnode, oldVnode)
      dir.def がコンポーネント更新された場合
        dirsWithPostpatch.push(dir)
      }
    }
  }

  (dirsWithInsert.length)の場合{
    // 実際のDOMがページに挿入され、このコールバックメソッドが呼び出されます const callInsert = () => {
      (i = 0 とします; i < dirsWithInsert.length; i++) {
        callHook(dirsWithInsert[i], '挿入済み', vnode, oldVnode)
      }
    }
    // VNode マージ挿入フック
    if (isCreated) {
      mergeVNodeHook(vnode、'挿入'、callInsert)
    } それ以外 {
      挿入()を呼び出す
    }
  }

  (dirsWithPostpatch.length)の場合{
    mergeVNodeHook(vnode, 'postpatch', () => {
      (i = 0 とします; i < dirsWithPostpatch.length; i++) {
        callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
      }
    })
  }

  作成する場合
    for (キー in oldDirs) {
      if (!newDirs[キー]) {
        // 存在しないので、バインドを解除します
        callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
      }
    }
  }
}

初めて作成する場合の実行プロセスは次のとおりです。

1.oldVnode === emptyNode、isCreate は true であり、現在の要素内のすべてのバインド フック メソッドが呼び出されます。

2. 命令に挿入フックがあるかどうかを確認します。ある場合は、挿入フックを VNode.data.hooks プロパティにマージします。

3. DOM のマウントが完了すると、VNode.data.hooks に挿入フックがある場合は、マウントされたすべてのノードに対してinvokeInsertHook が実行されます。それが呼び出され、命令にバインドされた挿入されたメソッドがトリガーされます。

通常、最初の作成には bind メソッドと insert メソッドのみが使用され、update と componentUpdated は bind と insert に対応します。コンポーネントの依存関係ステータスが変化すると、VNode diff アルゴリズムを使用してノードにパッチが適用されます。呼び出しプロセスは次のとおりです。

1. 応答データが変更された場合は、dep.notify を呼び出してデータの更新を通知します。

2. patchVNode を呼び出して、新しい VNode と古い VNode の差分更新を実行し、現在の VNode 属性 (updateDirectives メソッドに入る命令を含む) を完全に更新します。

3. 命令に更新フック メソッドがある場合は、更新フック メソッドを呼び出し、componentUpdated コールバックを初期化し、postpatch フックを VNode.data.hooks にマウントします。

4. 現在のノードとその子ノードが更新されると、ポストパッチフックがトリガーされます。つまり、命令のcomponentUpdatedメソッドです。

コアコードは次のとおりです。

// src/core/vdom/patch.js
関数 patchVnode (
    古いVノード、
    vノード、
    挿入されたVnodeQueue、
    所有者配列、
    索引、
    削除のみ
  ){
    // ...
    定数 oldCh = oldVnode.children
    定数ch = vnode.children
    // ノード属性を完全に更新します if (isDef(data) && isPatchable(vnode)) {
      (i = 0; i < cbs.update.length; ++i) の場合、cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }
    // ...
    if (isDef(データ)) {
    // postpatchフックを呼び出す if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

unbind メソッドは、ノードが破棄されるときにinvokeDestroyHook を呼び出しますが、ここでは詳細には説明しません。

3. 注意事項

カスタム命令を使用する場合、v-model は通常のテンプレート データ バインディングとはいくつかの点で異なります。たとえば、渡すパラメーター (v-xxx='param') は参照型ですが、データが変更されたときに命令の bind または inserted をトリガーすることはできません。これは、命令の宣言サイクルで、bind と inserted は初期化時に 1 回だけ呼び出され、その後は update と componentUpdated のみが使用されるためです。

ディレクティブの宣言ライフサイクルの実行順序は、bind -> inserted -> update -> componentUpdated です。ディレクティブが子コンポーネントのコンテンツに依存する必要がある場合は、対応するビジネス ロジックを componentUpdated に記述することをお勧めします。

Vue では、フック メソッド、イベント コールバックなど、多くのメソッドがループで呼び出されます。通常、呼び出しは try catch で囲まれます。これは、処理メソッドがエラーを報告してプログラム全体がクラッシュするのを防ぐためです。これは、開発プロセスのリファレンスとして使用できます。

IV. 要約

Vue のソースコード全体を見始めたときは、詳細や方法の多くがよくわかりませんでした。特定の機能ごとの実装を整理することで、徐々に Vue の全体像が見えてくると同時に、開発や使用における落とし穴を回避することができました。

上記は、Vue ディレクティブの実装原理を分析する詳細な内容です。Vue ディレクティブの原理の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Vue 3.0 カスタムディレクティブの使い方
  • Vueカスタム命令とその使用方法の詳細な説明
  • Vueカスタムディレクティブを使用してドラッグアンドドロッププラグインを構築する方法
  • Vue.js ソースコード解析のカスタム手順の詳細な説明
  • ボタンの権限判定を実装するためのVueカスタムv-has命令
  • Vue の基本的な手順の例のグラフィック説明
  • Vue 3 カスタムディレクティブ開発の概要
  • Vue3.0 カスタム命令(命令)知識の要約
  • 8つの非常に実用的なVueカスタム指示
  • Vueのカスタム命令の詳細な説明

<<:  Puppeteer を使用して Linux (CentOS) で Web ページのスクリーンショット機能を実装する

>>:  MySQL解凍版のインストール手順の詳しい説明

推薦する

KVM ベースの SRIOV パススルー構成とパフォーマンス テストの詳細な説明

SRIOVの導入、VFパススルー構成、パケット転送速度性能テスト目次1. SRIOVの紹介2. 環境...

MySQLの日付と時刻の間隔計算の分析例

この記事では、例を使用して、MySQL の日付と時刻の間隔計算について説明します。ご参考までに、詳細...

Vueがビデオアップロード機能を実装

この記事では、参考までに、ビデオアップロード機能を実現するためのVueの具体的なコードを紹介します。...

WeChatアプレットは固定ヘッダーとリストテーブルコンポーネントを実装します

目次必要:機能ポイントレンダリング実装のアイデア具体的なコード(react\taro3.0)特定のコ...

WeChatアプレットは日付と時刻に基づいた並べ替え機能を実装

最近、小さなプログラム プロジェクトを引き継いだのですが、リストを日付と時刻で並べ替えるという要件が...

CSS 属性セレクタを使用して HTML DNA を接合する方法

CSS 属性セレクターは素晴らしいです。大量のクラス名を追加することを回避し、コード内の問題を指摘す...

ウェブページでmp3またはフラッシュプレーヤーコードを再生する

コードをコピーコードは次のとおりです。 <オブジェクト id="player1&qu...

JavaScriptの再帰の詳細

目次1. 再帰とは何ですか? 2. 再帰を使って数学の問題を解く1. 1 * 2 * 3 * 4 …...

Web ページのソース ファイルを表示できない場合はどうすればよいですか?

Q: Outlook または IE のどちらを使用している場合でも、マウスを右クリックすると、ポッ...

CSS3 box-shadow プロパティの詳細な例

CSS3 - 影の追加(ボックスシャドウの使用) CSS3 - div またはテキストに影を追加する...

Linux CentOS6.5 yum インストール mysql5.6

この記事では、Linux yumを使用してmysql5.6をインストールする簡単な手順を参考までに紹...

MACでMYSQLデータベースのパスワードを忘れた場合の解決策

Mac オペレーティングシステムで MYSQL データベースのパスワードを忘れた場合の簡単な解決策1...

MySQL 5.7.23 バージョンのインストールチュートリアルと設定方法

MySQL を自分でインストールするのに 3 時間かかりました。チュートリアルはたくさんあるにもかか...

Vue のプロダクション環境と開発環境を切り替えてフィルターを使用する方法

目次1. 本番環境と開発環境を切り替える最初の方法: .envファイルを設定する2番目の方法2. フ...

CSSスタイルシートを効率的に使用するためのヒント: スタイルシートの力を最大限に活用する

インターネット経済の継続的な発展に伴い、インターネット上の専門ウェブサイト、公共サービスウェブサイト...