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パラメータを変更する際の注意事項

推薦する

Vue 初心者ガイド: 最初の Vue-cli スキャフォールディング プログラムの作成

1. Vue - 最初の vue-cli プログラムVueの開発はNodeJSに基づいています。実際...

WindowsシステムでMySQLデータベースを完全にアンインストールして、MySQLを再インストールします

1. コントロールパネルで、MySQLのすべてのコンポーネントをアンインストールします。コントロール...

docker ベースの mariadb のインストール構成プロセスの分析

1. インストール dockerhub を通じてインストールする mariadb のバージョンを検索...

Oracle を MySQL に置き換える際の問題と解決策

目次移行ツールアプリケーション変換mysql8.0 ドライバ パッケージを追加データソース構成の変更...

Linuxカーネルの浮動小数点演算のサポートに関する簡単な説明

現在、ほとんどの CPU は浮動小数点ユニット (FPU) をサポートしています。FPU は、プロセ...

RedisとMemcacheの比較と選び方

最近 redis を使っていて、とても便利だと感じているのですが、インメモリ データベースを選択する...

vue-routerフック関数はルーティングガードを実装します

目次概要グローバルフック関数ルーティング固有のフック関数コンポーネント内のフック関数概要ルートガード...

Linux で指定されたフォルダの各サブフォルダ内のファイル数を表示する

カウントスクリプト #!/bin/sh 引数の数=$# [ $numOfArgs -ne 1 ]の場...

MYSQLクエリデータの結果に自動的に番号を付ける方法

序文実際、クエリ中に結果に番号が付けられるこのような状況に遭遇したことは一度もありません。同僚が転職...

IE8は優れたエクスペリエンスを提供します: アクティビティ

今日は IE8 ベータ 1 (以下、IE8 と略します) をチラ見しました。IE8 は素晴らしい体験...

MySQL 8.0.18 のインストールと設定方法のグラフィックチュートリアル

この記事は、参考のためにMySQL 8.0.18のインストールと設定のグラフィックチュートリアルを記...

TypeScript 列挙の基本と例

目次序文TypeScript の列挙型とは何ですか? TypeScriptで列挙型を使用する際に注意...

JavaScript を使用して文字列内の最も繰り返しの多い文字を取得する方法

目次トピック分析する使用目的解決:コードは次のように実装されます。分析:配列とポインタ解決:コードは...

H5ウェイクアップアプリの実装方法と注意点のまとめ

目次序文APPメソッドにジャンプURLスキームメタタグユニバーサルリンクさまざまな使い方URLスキー...

Ubuntu 18.04 で MySQL のインストール時にパスワードが要求されない場合の解決方法

Ubuntu 1804 への MySQL 5.7 のインストールについて詳しく紹介します。 MySQ...