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解凍版のインストール手順の詳しい説明

推薦する

MySQLの暗黙的な変換について話す

作業の過程で、暗黙的な変換が発生するケースが数多くあります。暗黙的な変換は、クエリの速度低下を引き起...

MySQL 8.0.26 のインストールと簡易チュートリアル (インターネット上で最も完全)

目次1. MySQLをダウンロードする1.1 ダウンロード1.2 インストール1. MySQLをダウ...

CSS を使用して要素のスクロールバーを非表示にするサンプルコード

どの要素でもスクロールできるようにしながら、スクロールバーを非表示にするにはどうすればよいでしょうか...

Dockerコンテナの起動失敗を解決する方法

質問: コンピュータを再起動した後、docker の mysql コンテナを再起動できません。原因が...

JavaScript イベントバブリング、イベントキャプチャ、イベント委任の詳細な説明

1. イベントバブリング: JavaScript イベント伝播のプロセスでは、要素でイベントがトリガ...

CSS の歪んだ影の実装コード

この記事では、CSS ワープ シャドウの実装コードを紹介し、皆さんと共有します。詳細は以下の通りです...

コンテナDockerCommitを介してイメージを送信し、DockerPushでイメージをプッシュします。

ローカルでコンテナを作成した後、このコンテナに基づいてローカル イメージを作成し、このイメージを D...

Docker 可視化グラフィックツール portainer の詳細な説明

目次1. ポーテナーの紹介2. Portainer アーキテクチャの概要3. Portainerのイ...

CSS 3.0とビデオを組み合わせることでクリエイティブなオープニング効果を実現

CSS 3.0 とビデオを組み合わせて実現したクリエイティブなオープニングをご紹介します。効果は次の...

MySQLで時間別データと最後の時間別データの差をクエリするアイデアの詳細な説明

1. はじめに要件は、特定の時間範囲内で、1 時間ごとのデータと前の 1 時間ごとのデータの差と比率...

Tomcat+Mysql の高同時実行構成の最適化の説明

1.Tomcatの最適化構成(1)Tomcatのcatalina.batを変更するJavaをサーバー...

要素UIテーブルはドロップダウンフィルタリング機能を実現します

この記事の例では、要素UIテーブルにドロップダウンフィルタリングを実装するための具体的なコードを参考...

MySQL公式エクスポートツールmysqlpumpの使用

目次導入説明書実際の経験長所と短所総括する導入mysqlpump は mysqldump の派生です...

CentOS7 systemdにカスタムシステムサービスを追加する方法

システムド: CentOS 7のサービスsystemctlスクリプトは、/usr/lib/syste...

JS ベースの Ajax 同時リクエスト制御を実装する方法

目次序文Ajax シリアルおよびパラレルAjaxの同時リクエスト制御のための2つのソリューションPr...