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

推薦する

JavaScript でプライベート変数を宣言する 2 つの方法

序文JavaScript は、キーワードを使用してプライベート変数を宣言できる他の言語とは異なります...

ウェブサイトのアクセス速度を向上させるための徹底的な最適化に関するヒント

ウェブサイトのアクセス速度を向上させるための徹底的な最適化に関するヒント。ウェブサイトのアクセス速度...

CSSで制御可能な点線を実装する方法

序文CSS を使用して点線を生成するのは、フロントエンド開発者にとっては簡単です。一般的に、これを実...

MySQL 中断された接続警告ログの分析

序文:場合によっては、MySQL に接続されたセッションが異常終了することが多く、エラー ログに「通...

MySQL Order By 複数フィールドのソートルールのコード例

事前に言っておく気まぐれですが、MySQL の order by sorting にどのようなルール...

CSS ピクセルとさまざまなモバイル画面適応の問題に対する解決策

ピクセル解決通常、モニター解像度と呼ばれるものは、実際にはモニターの物理的な解像度ではなく、デスクト...

CocosCreator クラシック エントリー プロジェクト flappybird

目次開発環境ゲームエンジンのコンセプトCocos Creatorについてプロジェクト構造コード編集環...

マークアップ言語 - リスト再び

123WORDPRESS.COM HTML チュートリアル セクションに戻るには、ここをクリックして...

Linux環境変数ファイルの簡単な紹介

Linux システムでは、環境変数は適用範囲に応じて、システムレベルの環境変数とユーザーレベルの環境...

TomcatとJDKのバージョンの対応と各Tomcatバージョンの機能

Apache Tomcat は、Java Servlet および Java Server Pages...

DockerでVueプロジェクトをデプロイする方法を教えます

1.前面に書きます:軽量仮想化テクノロジーとして、Docker には継続的インテグレーション、バージ...

Vue 3 での watch と watchEffect の新しい使い方

目次1. 時計の新しい使い方1.1. ウォッチの使用構文1.2. 複数の属性値を監視する1.3. 参...

W3C チュートリアル (14): W3C RDF および OWL アクティビティ

RDF と OWL は、2 つの重要なセマンティック ウェブ テクノロジーです。 RDF と OWL...

vue+element-uiはヘッドナビゲーションバーコンポーネントを実装します

この記事では、vue+element-uiでヘッドナビゲーションバーコンポーネントを実装するための具...