Vue命令の動作原理と実装方法

Vue命令の動作原理と実装方法

Vue の紹介

現在のビッグフロントエンドの時代は、混乱と衝突の時代です。世界は多くの派閥に分かれており、主にVue、React、Angularが主導し、フロントエンドフレームワークの三つ巴の状況を形成しています。フロントエンドフレームワークにおけるVueの位置づけは、かつてのjQueryのようなものであり、そのシンプルさと開発効率の高さから、フロントエンドエンジニアにとって必須のスキルの一つとなっています。

Vue は、サードパーティのプラグインと UI コンポーネント ライブラリを完全に統合するプログレッシブ JavaScript フレームワークです。jQuery との最大の違いは、開発者が DOM ノードを直接操作しなくても、Vue ではページのレンダリング コンテンツを変更できることです。アプリケーション開発者は、特定の HTML、CSS、JavaScript を基盤として、すぐに使い始めて、エレガントで簡潔なアプリケーション モジュールを開発できます。

序文

カスタム命令は、コンポーネントの次に Vue で 2 番目に頻繁に使用され、5 つのライフサイクル フック ( bindinsertedupdatecomponentUpdatedunbindが含まれます。この記事では、Vue ディレクティブの動作原理を紹介します。この記事から、次のことが得られます。

  • 指令の仕組み
  • 使用上の注意

基本的な使い方

公式サイトの場合:

<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')
</スクリプト>

コマンドの動作

初期化

グローバル 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) を返す {
    // ...
  }
}

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

テンプレートのコンパイルは、命令パラメータを解析することです。具体的に分解された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,
      価値: ""
    }
  ]、
  // ...
}

レンダリング方法の生成

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

(これ){
    _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
            }
        }
    })])
}

VNode を生成する

vue のディレクティブは、DOM の操作を容易にするように設計されています。VNode を生成するとき、ディレクティブは追加の処理を行いません。

実際の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. ディレクティブにinsertedフックがあるかどうかを確認します。ある場合は、挿入フックをVNode.data.hooksプロパティにマージします。
  3. DOM のマウントが完了すると、 VNode.data.hooksに挿入フックがある場合は、マウントされたすべてのノードに対してinvokeInsertHookが実行されます。それが呼び出され、命令にバインドされた挿入されたメソッドがトリガーされます。

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

  1. 応答データが変更された場合は、dep.notify を呼び出してデータの更新を通知します。
  2. patchVNode を呼び出して、新しい VNode と古い VNode の差分更新を実行し、現在の VNode 属性 (updateDirectives メソッドに入る命令を含む) を完全に更新します。
  3. ディレクティブに更新フック メソッドがある場合は、更新フック メソッドを呼び出し、 componentUpdatedコールバックを初期化し、ポストパッチ フックをVNode.data.hooksにマウントします。
  4. 現在のノードとその子ノードが更新されると、 postpatch hooks 、つまりディレクティブの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を呼び出しますが、ここでは詳細には説明しません。

予防

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

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

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

まとめ

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

GitHub

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

以下もご興味があるかもしれません:
  • Vue v-modelディレクティブの実装原理に関する簡単な説明
  • Vue における v-on イベント監視命令の基本的な使用方法の詳細な説明
  • Vue の動的属性データバインディング (v-bind 命令) を 1 つの記事で理解する
  • v-html 命令を通じて vue によってレンダリングされたリッチ テキストのスタイルを変更できない問題の解決策
  • vue アンカーディレクティブ v-anchor の使用に関する簡単な説明

<<:  Ubuntu 18.04 に VMware Tools をインストールする際のエラーを解決する

>>:  MySQLのinnodb_data_file_pathパラメータを変更する際の注意事項

推薦する

MySQL 同期遅延が発生したときに Seconds_Behind_Master が 0 のままになる理由

目次問題の説明原理分析問題分析拡大する総括する問題の説明ユーザーはプライマリ データベースに対して変...

JavaScript における Arguments オブジェクトの使用に関する詳細な説明

目次序文議論の基本概念議論の役割実パラメータと仮パラメータの数を取得する実際のパラメータ値を変更する...

Mysql トランザクション分離レベルの読み取りコミットの詳細な説明

MySQL トランザクション分離レベルを表示する mysql> '%isolation...

手の動きをリアルタイムで監視するための Handtrack.js ライブラリ (推奨)

【はじめに】: Handtrack.jsは、ブラウザ上で直接リアルタイムの手の動きの追跡と検出を実...

jQuery Ajax チャットボットの実装事例

チャットボットは多くの手作業を省くことができ、顧客サービス、天気予報対応など、さまざまな状況で使用で...

ウェブページをデザインする際に注意すべきいくつかの問題

Web デザインは、個人の好みや Web ページの内容に応じて、デザインのレイアウトが常に変化します...

Tomcat メモリ オーバーフロー問題の解決経験

少し前に、製品バージョンをテスト用にテスターに​​提出したのですが、テスト結果はまったく予想外のもの...

Nexusプライベートサーバー構築原理とチュートリアル分析

1つ。 Nexus プライベート サーバーを構築する理由は何ですか?社内の開発メンバーは全員外部ネッ...

HTML の <input> タグの詳細な説明と、それを無効にする方法

定義と使用法<input> タグはユーザー情報を収集するために使用されます。 type ...

JavaScriptのモジュール性を理解する方法

目次1. ブラウザのサポート2.エクスポートモジュールのエクスポート3. モジュールをインポートする...

JavaScript初心者がよく犯す間違い

目次序文undefined と null の混同紛らわしい数値の加算と文字列の連結戻り文の改行の問題...

nginx ロケーション優先度の詳細な説明

場所表現タイプ~ は大文字と小文字を区別して通常の一致を実行することを示します~*は大文字と小文字を...

vuex で履歴を実装するためのサンプルコード

私は最近、ユーザー操作を元に戻す、またはやり直す機能を備えたビジュアル操作プラットフォームを開発して...

Nginx メモリプールのソースコード分析

目次メモリプールの概要1. nginxデータ構造2. nginxはOSからスペースngx_creat...

Dockerコンテナ監視とログ管理の実装プロセス分析

Docker の導入規模が大きくなると、コンテナを監視する必要があります。一般的に、Docker に...