Vue.js ソースコード解析のカスタム手順の詳細な説明

Vue.js ソースコード解析のカスタム手順の詳細な説明

序文

コア機能のデフォルトの組み込みディレクティブ (v-model および v-show) に加えて、Vue ではカスタム ディレクティブを登録することもできます。

公式サイトの紹介は比較的抽象的で、非常に壮大に思えます。カスタム命令についての私の個人的な理解は、カスタム命令が一部の DOM 要素またはコンポーネントに作用する場合、要素は最初にレンダリングされるとき、親ノードに挿入されるとき、更新されるとき、またはアンバインドされるときに、いくつかの特定の操作 (フック関数 ()) を実行できるということです。

カスタムディレクティブを登録する方法は 2 つあります。1 つはグローバル登録で、Vue.directive (ディレクティブ名、構成パラメータ) を使用して登録します。登録後は、すべての Vue インスタンスで使用できます。もう 1 つはローカル登録です。Vue インスタンスを作成するときに、directives 属性を通じてローカルディレクティブが作成されます。ローカルカスタムディレクティブは、現在の Vue インスタンスでのみ使用できます。

カスタム命令は、次のフック関数にバインドできます。

·bind; は一度だけ呼び出されます。要素がDOMノードにレンダリングされた後、ディレクティブモジュールの初期化作業が実行されるときに呼び出されます。ここで、1回限りの初期化設定を行うことができます。
· 挿入; バインドされた要素が親ノードに挿入されたときに呼び出されます (親ノードが存在することのみが保証され、必ずしもドキュメントに挿入されているとは限りません)。
·update; コンポーネントの VNode が更新されたときに呼び出されますが、その子 VNode が更新される前に発生することもあります。命令の値は変更されている場合と変更されていない場合があります。
·componentUpdated; 命令が配置されているコンポーネントの VNode とその子 VNode がすべて更新された後に呼び出されます。
unbind; 命令が要素からアンバインドされたときに 1 回だけ呼び出されます。

各フック関数には、el (対応する DOM ノード参照)、binding (命令に関する拡張情報、オブジェクト)、vnode (ノードに対応する仮想 VNode)、oldVnode (以前の VNode、update フックと componentUpdated フックでのみ使用可能) の 4 つのパラメータがあります。

バインド フック関数が実行されると、DOM 要素はレンダリングされますが、親要素には挿入されません。次に例を示します。

<!DOCTYPE html>
<html lang="ja">
<ヘッド>
    <メタ文字セット="UTF-8">
    <title>ドキュメント</title>
    <script src="vue.js"></script>
</head>
<本文>
    <div id="d"><input type="" name="" v-focus></div>
    <スクリプト>
        Vue.directive('focus', {       
            bind:function(el){console.log(el.parentElement);}, // 挿入された親ノードを出力します: function (el) {console.log(el.parentElement);el.focus()} // 親ノードを出力し、現在の要素にフォーカスを当てます })
        var app = new Vue({el:"#d"})
    </スクリプト>
</本文>
</html>

出力は次のようになります。

入力要素が自動的にフォーカスを取得し、コンソール出力が次のようになっていることがわかります。

bind() フックの場合、Vue は bind() フックを実行した後にのみ現在の要素を親要素の子ノードに挿入するため、その親ノードを取得できないことがわかります。

ソースコード分析

processAttrs() 関数は、テンプレートを解析して DOM を AST オブジェクトに変換するときに次のように実行されます。

function processAttrs (el) { //Vue 属性を解析 var list = el.attrsList; 
  var i、l、名前、rawName、値、修飾子、isProp;
  for (i = 0, l = list.length; i < l; i++) { //各属性を走査します name = rawName = list[i].name;
    値 = リスト[i].値;
    if (dirRE.test(name)) { //属性がv-、@、または:で始まる場合、これはVue内部ディレクティブであることを意味します //要素を動的としてマークします
      el.hasBindings = true;
      // 修飾子
      修飾子 = parseModifiers(名前);
      if (修飾子) {
        name = name.replace(modifierRE, '');
      }
      if (bindRE.test(name)) { // v-bind //bindRD は /^:|^v-bind:/ に等しい、つまり属性が v-bind 命令 /*v-bind 分岐*/ の場合
      } else if (onRE.test(name)) { // v-on
        /* v-on の分岐 */
      } else { // 通常のディレクティブ
        name = name.replace(dirRE, ''); //命令のプレフィックスを削除します。たとえば、v-show は実行後に show と同じになります。
        // 引数を解析する
        var argMatch = name.match(argRE);
        var arg = argMatch && argMatch[1];
        if (引数) {
          name = name.slice(0, -(arg.length + 1));
        }
        addDirective(el, name, rawName, value, arg, modifiers); //addDirective を実行して、el にディレクティブ属性を追加します。if ("development" !== 'production' && name === 'model') {
          エイリアスモデルをチェックします(el、値);
        }
      }
    } それ以外 {
      /*非Vueディレクティブブランチ*/
    }
  }
}

addDirective は、次のように、命令情報を保存するディレクティブ属性を AST オブジェクトに追加します。

function addDirective ( // 6561行目は命令に関連しています。ASTオブジェクトelに命令elの情報を持つディレクティブ属性を追加します。
  名前、
  生の名前、
  価値、
  引数、
  修飾語
){
  (el.directives || (el.directives = [])).push({ name: name, rawName: rawName, value: value, arg: arg, modifiers: modifiers });
  el.plain = false;
}

ここで例の p 要素を実行すると、対応する AST オブジェクトは次のようになります。

次に、generate が rendre 関数を生成すると、次のように genDirectives() 関数が実行され、AST がレンダリング関数に変換されます。

(this){return _c('div',{attrs:{"id":"d"}},[_c('input',{directives:[{name:"focus",rawName:"v-focus"}],attrs:{"type":"","name":""}})])}

最後に、レンダリングが完了すると、次のようにディレクティブ モジュールの create フック関数が実行されます。

var directives = { // 行 6173 ディレクティブ module create: updateDirectives, // DOM 作成後のフック update: updateDirectives,
  破棄: 関数 unbindDirectives (vnode) {
    ディレクティブを更新します(vnode、空のノード)。
  }
}

function updateDirectives (oldVnode, vnode) { //行 6181 oldVnode: 古い Vnode、vnode は更新時にのみ使用可能: 新しい VNode
  if (oldVnode.data.directives || vnode.data.directives) {
    _update(古いVnode、vnode);
  }
}

_updat は、次のように処理命令を初期化および更新するために使用されます。

function _update (oldVnode, vnode) { // 6187 行目の初期化/更新命令 var isCreate = oldVnode === emptyNode; // 初期化ですか var isDestroy = vnode === emptyNode;
  var oldDirs = normalizeDirectives$1(oldVnode.data.directives、oldVnode.context);          
  var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context); //パラメータを正規化するためにnormalizeDirectives$1()関数を呼び出します var dirsWithInsert = [];
  var dirsWithPostpatch = [];

  var キー、oldDir、dir;
  for (key in newDirs) { //newDirsを走査する
    oldDir = oldDirs[key]; // 古い方のキーディレクティブ情報Vnodedir = newDirs[key]; // 古い方のキーディレクティブ情報vnodeif (!oldDir) { // oldDir が存在しない場合は新しいディレクティブです // 新しいディレクティブ、bind
      callHook$1(dir, 'bind', vnode, oldVnode); //callHook$1() 関数を呼び出します。パラメータ 2 は bind です。つまり、v-focus 命令の bind 関数を実行します。if (dir.def && dir.def.inserted) { //挿入されたフック関数が定義されている場合 dirsWithInsert.push(dir); //それを dirsWithInsert 配列に保存します}
    } それ以外 {
      // 既存のディレクティブを更新
      dir.oldValue = oldDir.value;
      callHook$1(dir, 'update', vnode, oldVnode);
      dir.def がコンポーネント更新された場合
        dirsWithPostpatch.push(dir);
      }
    }
  }
  if (dirsWithInsert.length) { // dirsWithInsertが存在する場合(つまり、挿入されたフック関数がバインドされている場合)
    var callInsert = function () { // dirsWithInsert 内の各関数を実行する callInsert 関数を定義します for (var i = 0; i < dirsWithInsert.length; i++) {
        callHook$1(dirsWithInsert[i], '挿入済み', vnode, oldVnode);   
      }
    };
    if (isCreate) { // 初期化されている場合 mergeVNodeHook(vnode, 'insert', callInsert); // mergeVNodeHook() 関数を呼び出す } else {
      挿入() を呼び出します。
    }
  }

  (dirsWithPostpatch.length)の場合{        
    mergeVNodeHook(vnode, 'postpatch', 関数() {
      (var i = 0; i < dirsWithPostpatch.length; i++) {
        callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
      }
    });
  }

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

作者: Great Desert QQ: 22969969

バインド フック関数の場合は直接実行されますが、挿入されたフック関数の場合は関数が dirsWithInsert 配列に保存され、callInsert 関数が定義されます。関数はスコープを通じて dirsWithInsert 変数にアクセスし、変数をトラバースして挿入された各フック関数を順番に実行します。

mergeVNodeHook() フック関数は、対応する Vnode のデータにフック属性として挿入を保存します。Vnode が親ノードに挿入されると、次のようにフックが呼び出されます。

function mergeVNodeHook (def, hookKey, hook) { // 2074行目はVNodeフックをマージします。関数def: VNode hookKey: (イベント名、例: insert) hook: コールバック関数if (def instanceof VNode) { // defがVNodeの場合
    def = def.data.hook || (def.data.hook = {}); // VNode.data.hook にリセットします。VNode.data.hook が存在しない場合は、空のオブジェクトに初期化します。注: 通常のノードでは VNode.data.hook は存在しません。
  }
  var 呼び出し元;
  var oldHook = def[hookKey];
 
  関数 wrappHook() {     
    hook.apply(this, arguments); // 最初にフック関数を実行します // 重要: マージされたフックを削除して、1 回だけ呼び出されるようにします
    // メモリリークを防ぐ
    remove(invoker.fns, wrappHook); //invoker.fns から wrappHook を削除して、パッケージが 1 回だけ実行されるようにします}

  if (isUndef(oldHook)) { //oldHookが存在しない場合、つまりhookKeyフック関数が以前に定義されていない場合 //既存のフックはありません
    invoker = createFnInvoker([wrappedHook]); //createFnInvoker() を直接呼び出して、実行されるコールバック関数をパラメータとするクロージャ関数を返します} else {
    /* イスタンブールは無視します */
    if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {
      // すでにマージされた呼び出し元
      呼び出し元 = oldHook;
      呼び出し元.fns.push(ラップされたフック);
    } それ以外 {
      // 既存のプレーンフック
      呼び出し側 = createFnInvoker([oldHook, wrappHook]);
    }
  }

  呼び出し側がマージされる = true;
  def[hookKey] = invoker; // defのhookKeyプロパティを新しいinvokerを指すように設定する
}

createFnInvoker は v-on 命令に対応する関数です。同じ API を使用します。実行後、次のように、input に対応する VNode.data.hook にinvoker を挿入します。

最後に、VNode が親ノードに挿入された後、invokeCreateHooks() 関数が実行されます。この関数は VNode.hook.insert を走査し、各関数を順番に実行し、カスタム定義の挿入フック関数を実行します。

要約する

Vue.js ソースコード解析のカスタム手順に関するこの記事はこれで終わりです。より関連性の高い Vue.js カスタム手順については、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Vue.js ディレクティブのカスタム命令の詳細な説明
  • Vue.jsカスタム手順の学習と使用の詳細な説明
  • Vue.js でカスタム ドロップダウン メニューの指示を実装する方法について簡単に説明します。
  • Vue.js コンポーネントの再利用性ミックスインメソッドとカスタムディレクティブの詳細な説明
  • vue.js 内部カスタム命令とグローバルカスタム命令の詳細な実装 (using ディレクティブ)
  • Vue.jsカスタム命令の基本的な使用方法の詳細

<<:  MySQLマスタースレーブデータベース構築方法の詳細な説明

>>:  nginx + fastcgi を使用して画像認識サーバーを実装する

推薦する

MySQL 8.0.19 winx64 インストールチュートリアルと Windows 10 での初期パスワードの変更

この記事では、参考までにMySQL 8.0.19 winx64のインストールチュートリアルを紹介しま...

Windows Server 2019 のセットアップ方法 (画像とテキスト付き)

1. Windows Server 2019 のインストールVmware に Windows Se...

Win10にnginxをインストールして設定するプロセス

1. はじめにNginx は、無料のオープンソースの高性能 HTTP サーバーおよびリバース プロキ...

MySQL 8.0.12 インストール設定方法とパスワード変更

この記事ではMySQL 8.0.12のインストールと設定方法を参考までに記録します。具体的な内容は以...

HTML シンプルショッピング数量アプレット

この記事では、参考までにシンプルなHTMLショッピング数量アプレットを紹介します。具体的な内容は次の...

Vue ミックスインの詳しい説明

目次ローカルミックスイングローバル ミックスイン要約するローカルミックスイン <テンプレート&...

HTML テーブルの境界線を設定する際のヒント

HTML を初めて使用する多くの人にとって、テーブル <table> は最もよく使用され...

JSは単純なフィルタリングから複数条件のフィルタリングまで配列フィルタリングを実装します

目次単一条件単一データフィルタリング単一条件複数データフィルタリング複数の条件付きデータフィルタリン...

Linux で at および cron スケジュールタスクをカスタマイズする方法

Linux システムには 2 種類のスケジュールされたタスクがあります。1 つは 1 回だけ実行され...

テーブルの追加と削除の操作を実装する js

この記事の例では、テーブルを追加および削除するためのjsの具体的なコードを参考までに共有しています。...

CentOS7.4 に MySQL 5.7.26 をインストールするための詳細なチュートリアル

CentOS にはデフォルトで MariaDB がインストールされていますが、これは MySQL の...

Docker で最初のアプリケーションをデプロイする方法

前回の記事では、Docker Desktop をインストールし、Kubernetes を有効にしまし...

HTML タグ: サブタグと sup タグ

今日はあまり使わないHTMLタグ「subタグ」と「supタグ」を紹介します。関連記事: HTML タ...

CentOS7 で yum ソースをインストールし、コマンド rz と sz をアップロードおよびダウンロードする方法 (画像付き)

** CentOS7 で yum ソースをインストールし、rz および sz コマンドをアップロー...

Vueは小さな天気予報アプリケーションを実装します

これは私が Vue フレームワークを独学していたときに真似したウェブサイトです。いくつかの都市の天気...