Vueデータ双方向バインディング実装方法

Vueデータ双方向バインディング実装方法

1. はじめに

この記事は、Vue ソースコードを学習している初心者に適しています。これを読めば、Vue における双方向データバインディングの原理を大まかに理解でき、Observer、Compile、Wathcer の 3 つの主要な役割 (下図参照) とその機能を理解できます。

この記事では、双方向データ バインディングのシンプルなバージョンを実装する手順を段階的に説明します。各ステップでは、そのステップで解決する問題と、コードがこのように記述されている理由を詳細に分析します。したがって、この記事を読んだ後、双方向データ バインディングのシンプルなバージョンを自分で実装できるようになることを願っています。

2. コードの実装

2.1 目的分析

この記事で達成される効果は次の図に示されています。

この記事で使用されている HTML と JS の主なコードは次のとおりです。

<div id="アプリ">
  <h1 v-text="メッセージ"></h1>
  <input type="text" v-model="msg">
  <div>
    <h1 v-text="msg2"></h1>
    <input type="text" v-model="msg2">
  </div>
</div>
vm = new Vue({
    el: "#app",
    データ: {
      メッセージ: "こんにちは世界",
      メッセージ2: 「こんにちは、シャオフェイ」
    }
  })

これを 3 つのステップで実行します。

  • ステップ 1: データ内のデータをページに同期して、M ==> V の初期化を実現します。
  • ステップ 2: 入力ボックスに値が入力されると、新しい値がデータと同期され、V ==> M のバインディングが実現されます。
  • ステップ 3: データが更新されると、ページの変更がトリガーされ、M ==> V のバインディングが実現されます。

2.2 実装プロセス

2.2.1 エントリーコード

まず、オプション オブジェクトを受け取る Vue クラスを作成する必要があります。同時に、オプション オブジェクトに有効な情報を保存する必要があります。

次に、Observer、Compile、Wathcer という 3 つの主要モジュールがあります。このうち、Observer はデータ ハイジャックに使用され、Compile は要素の解析に使用され、Wathcer はオブザーバーです。次のようなコードを記述できます。(Observer、Compile、Wathcer の 3 つの概念については後で詳しく説明するので、詳しく学習する必要はありません)。

クラスVue {
    // 渡されたオブジェクトを受け取るconstructor(options) {
      // 有効な情報を保存します this.$el = document.querySelector(options.el);
      this.$data = オプション.data;

      //コンテナー: {属性 1: [wathcer1、wathcer2...]、属性 2: [...]}、各属性オブザーバーを格納するために使用されます this.$watcher = {};

      // 要素の解析: コンパイルの実装
      this.compile(this.$el); // 要素を解析するには、要素を渡す必要があります // データのハイジャック: Observer の実装
      this.observe(this.$data); // データをハイジャックするには、データを渡す必要があります}
    コンパイル() {}
    観察する() {}
  }

2.2.2 ページの初期化

このステップでは、ページを初期化する必要があります。つまり、v-text および v-model 命令を解析し、data 内のデータをページにレンダリングする必要があります。

このステップの鍵は、compile メソッドを実装することです。では、el 要素をどのように解析するのでしょうか?考え方は次のとおりです。

  • まず、el の下にあるすべての子ノードを取得し、これらの子ノードをトラバースする必要があります。子ノードに子ノードがある場合は、再帰の考え方を使用する必要があります。
  • 子ノードをトラバースして、命令を含むすべての要素を見つけ、対応するデータをページにレンダリングします。

コードは次のとおりです: (主にコンパイル部分を見てください)

クラスVue {
    // 渡されたオブジェクトを受け取るconstructor(options) {
      // 役に立つ情報を取得します this.$el = document.querySelector(options.el);
      this.$data = オプション.data;

      // コンテナ: {attribute1: [wathcer1, wathcer2...], attribute2: [...]}
      this.$watcher = {};

      // 2. 要素の解析: コンパイルの実装
      this.compile(this.$el); // 要素を解析するには、要素を渡す必要があります // 3. データのハイジャック: Observer の実装
      this.observe(this.$data); // データをハイジャックするには、データを渡す必要があります}
    コンパイル(el) {
      // 要素の下の各子ノードを解析し、el.children を取得します。
      // 注意: children は要素セットを返し、childNodes はノードセットを返します。let nodes = el.children;

      // 各子ノードの命令を解析します for (var i = 0, length = nodes.length; i < length; i++) {
        ノードをノード[i]とします。
        // 現在のノードに子要素がある場合は、ノードを再帰的に解析します if(node.children){
          これをコンパイルします(ノード);
        }
        // v-textディレクティブを持つ要素を解析します if (node.hasAttribute("v-text")) {
          attrVal = node.getAttribute("v-text"); とします。
          node.textContent = this.$data[attrVal]; // ページをレンダリングする}
        // v-model ディレクティブを持つ要素を解析します if (node.hasAttribute("v-model")) {
          attrVal = node.getAttribute("v-model"); とします。
          ノードの値 = this.$data[attrVal];
        }
      }
    }
    観察(データ) {}
  }

このようにして、ページの初期化が実現しました。

2.2.3 ビューはデータに影響を与える

input には v-model 命令があるため、入力ボックスに文字が入力されると、それに応じて data にバインドされたデータが変更されるような関数を実装する必要があります。

入力イベントを入力要素にバインドできます。イベントの効果は、データ内の対応するデータを入力の値に変更することです。

この部分の実装コードは比較的単純です。マークした箇所を見れば理解できます。コードは以下のとおりです。

クラスVue {
    コンストラクタ(オプション) {
      this.$el = document.querySelector(options.el);
      this.$data = オプション.data;
      
      this.$watcher = {};  

      this.compile(this.$el);

      this.observe(this.$data);
    }
    コンパイル(el) {
      ノードを el.children とします。

      (var i = 0、長さ = nodes.length; i < length; i++) {
        ノードをノード[i]とします。
        if(ノード.children){
          これをコンパイルします(ノード);
        }
        ノードが属性を持っている場合("v-text")
          attrVal = node.getAttribute("v-text"); とします。
          node.textContent = this.$data[attrVal];
        }
        ノードが属性を持っている場合("v-model")
          attrVal = node.getAttribute("v-model"); とします。
          ノードの値 = this.$data[attrVal];
          // ここを見て! !あとコードは3行だけです! !
          node.addEventListener("入力", (ev)=>{
            this.$data[attrVal] = ev.target.value;
            // ここで実行してみてください: console.log(this.$data),
            // 入力ボックスにテキストを入力するたびに、データ内の msg 値も変化することがわかります})
        }
      }
    }
    観察(データ) {}
  }

2.2.4 データ影響ビュー

これまでのところ、入力ボックスに文字を入力すると、データ内のデータが自動的に更新されることを実現しました。

このセクションの主なタスクは、データ内のデータが更新されると、データにバインドされた要素がページ上のビューを自動的に更新することです。具体的なアイデアは以下の通りです。

1) ページを更新するための更新メソッドを持つ Watcher クラスを実装します。オブザーバーコードは次のとおりです。

クラスウォッチャー{
    コンストラクター(ノード、updatedAttr、vm、式){
      //渡された値を保存します。これらの値はページをレンダリングするときに使用されます。this.node = node;
      this.updatedAttr = 更新されたAttr;
      this.vm = vm;
      this.expression = 式;
      これを更新します。
    }
    アップデート(){
      this.node[this.updatedAttr] = this.vm.$data[this.expression];
    }
  }

2) 考えてみましょう。どのデータにオブザーバーを追加すればよいでしょうか?データにオブザーバーを追加するタイミングはいつですか?

要素を解析するときに、v-text および v-model 命令が解析されると、この要素は両方向でデータにバインドされる必要があることを意味するため、この時点でコンテナーにオブザーバーを追加します。次のようなデータ構造を使用する必要があります: {属性 1: [wathcer1, wathcer2...], 属性 2: [...]}。わかりにくい場合は、次の図を参照してください。

vue インスタンスに $watcher オブジェクトがあることがわかります。$watcher の各属性はバインドする必要がある各データに対応しており、値はデータを監視したオブザーバーを格納するために使用される配列です。 (注: Vue のソースコードでは、ここで言及した配列に対応する Dep というクラスが具体的に作成されます。この記事は簡易版なので、詳しくは紹介しません。)

3) データのハイジャック: オブジェクトのアクセサー プロパティの getter と setter を使用して、データが更新されたときにアクションをトリガーします。このアクションの主な目的は、データを監視したすべてのオブザーバーが更新メソッドを実行できるようにすることです。

要約すると、このセクションで必要なことは次のとおりです。

  1. Watcher クラスを実装します。
  2. 命令を解析するときにオブザーバーを追加します(つまり、コンパイル メソッド内)。
  3. データハイジャックを実装します(観察メソッドを実装します)。

完全なコードは次のとおりです。

  クラスVue {
    // 渡されたオブジェクトを受け取るconstructor(options) {
      // 役に立つ情報を取得します this.$el = document.querySelector(options.el);
      this.$data = オプション.data;

      // コンテナ: {attribute1: [wathcer1, wathcer2...], attribute2: [...]}
      this.$watcher = {};

      // 要素の解析: コンパイルの実装
      this.compile(this.$el); // 要素を解析するには、要素を渡す必要があります // データのハイジャック: Observer の実装
      this.observe(this.$data); // データをハイジャックするには、データを渡す必要があります}
    コンパイル(el) {
      // 要素の下の各子ノードを解析し、el.children を取得します。
      // 拡張: children は要素セットを返し、childNodes はノードセットを返します。let nodes = el.children;

      // 各子ノードの命令を解析します for (var i = 0, length = nodes.length; i < length; i++) {
        ノードをノード[i]とします。
        // 現在のノードに子要素がある場合は、ノードを再帰的に解析します if (node.children) {
          これをコンパイルします(ノード);
        }
        ノードが属性を持っている場合("v-text")
          attrVal = node.getAttribute("v-text"); とします。
          // node.textContent = this.$data[attrVal]; 
          // ウォッチャーはインスタンス化時に update を呼び出し、このコード行を置き換えます/**
           * ノード データを更新するときに Watchcer が使用する必要があるデータを想像してみてください。 
           * egpinnerHTML = vm.$data[msg]
           * 渡されるパラメータは、現在のノード、更新されるノード属性、vue インスタンス、バインドされたデータ属性です。*/
          // コンテナにオブザーバーを追加します: {msg1: [Watcher, Watcher...], msg2: [...]}
          if (!this.$watcher[attrVal]) {
            this.$watcher[attrVal] = [];
          }
          this.$watcher[attrVal].push(新しいウォッチャー(ノード、"innerHTML"、this、attrVal))
        }
        ノードが属性を持っている場合("v-model")
          attrVal = node.getAttribute("v-model"); とします。
          ノードの値 = this.$data[attrVal];

          node.addEventListener("入力", (ev) => {
            this.$data[attrVal] = ev.target.value;
          })

          if (!this.$watcher[attrVal]) {
            this.$watcher[attrVal] = [];
          }
          // 上で使用した innerHTML とは異なり、ここでの入力は値属性を使用します this.$watcher[attrVal].push(new Watcher(node, "value", this, attrVal))
        }
      }
    }
    観察(データ) {
      Object.keys(data).forEach((key) => {
        let val = data[key]; // このvalは常にメモリに保存されます。data[key]にアクセスするたびに、このvalにアクセスします
        Object.defineProperty(データ、キー、{
          得る() {
            return val; // ここで直接 data[key] を返すことはできません。そうしないと無限ループに陥ります},
          set(newVal) {
            val !== newVal の場合 {
              val = newVal; // 同様に、data[key] をここで直接設定することはできないため、無限ループが発生します this.$watcher[key].forEach((w) => {
                w.update();
              })
            }
          }
        })
      })
    }
  }

  クラスウォッチャー{
    コンストラクター(ノード、updatedAttr、vm、式) {
      //渡された値を保存します。this.node = node;
      this.updatedAttr = 更新されたAttr;
      this.vm = vm;
      this.expression = 式;
      これを更新します。
    }
    アップデート() {
      this.node[this.updatedAttr] = this.vm.$data[this.expression];
    }
  }

  vm = new Vue({
    el: "#app",
    データ: {
      メッセージ: "こんにちは世界",
      メッセージ2: 「こんにちは、シャオフェイ」
    }
  })

この時点でコードは完成です。

3. 今後の予定

デザイン パターンの知識を使用して、上記のソース コードの問題を分析し、それを Vue ソース コードと比較します。これは、Vue ソース コードの分析と見なされます。

以上がVueデータ双方向バインディングの実装方法の詳細な内容です。Vueデータ双方向バインディングの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Vue2.0/3.0双方向データバインディングの実装原理の詳細説明
  • vuexの強制リフレッシュによるデータ損失問題の分析
  • Vue はデータの変更をどのように追跡しますか?
  • Vue+canvas は、ウォーターフォール チャートを上から下までリアルタイムに更新する効果を実現します (QT と同様)
  • Vueデータ割り当て問題の解決
  • Vueはデータを初期状態にリセットします
  • Vue コンポーネント値転送中のデータ損失の分析と解決
  • SpringBoot+Vueでデータ追加機能を実現
  • 手書きの Vue2.0 データハイジャックの例
  • Vueでデータを読み取るためにこれを悪用しないでください
  • Vue でデータコレクターを設計する

<<:  Linuxにおけるselinuxの基本設定チュートリアルの詳細な説明

>>:  MySQL でローカル ユーザーを作成し、データベース権限を付与する方法の例

推薦する

aタグ内のテキストを非表示にして画像を表示するには?360モードレンダリングに対応

多くの場合、画像を表示する<a>タグのスタイルに遭遇しますが、タグ内にテキストがあり、そ...

Docker に influxdb をインストールするための詳細なチュートリアル (パフォーマンス テスト)

1. 前提条件1. プロジェクトが展開されました2. Dockerはすでにインストールされている2...

HTML 9グリッドレイアウトの実装方法

ウェブサイトのレイアウトの多様化は、当社のフロントエンドの得意分野です。最近、UC ブラウザのデフォ...

mysql 10進データ型変換の実装

最近、次のデータ型のデータベースに遭遇しました:decimal(14,4)発生した問題は次のとおりで...

MySQL ルートパスワードを変更する 4 つの方法 (要約)

方法1: SET PASSWORDコマンドを使用するまずMySQLにログインします。フォーマット: ...

テキストエリアの使用に関する注意事項

なぜテキストエリアについて具体的に言及するのでしょうか?なぜなら、textarea ノードは実際には...

検索履歴を実装するjQueryプラグイン

毎日jQueryプラグイン - 検索履歴を作成するためのものです。参考までに、具体的な内容は次のとお...

最新のウェブフロントエンドフレームワーク10選を紹介(翻訳)

Web 開発の世界では、フレームワークは非常に一般的です。新しいフレームワークやテンプレートが毎日の...

Linux netfilter/iptables の知識ポイントの詳細な説明

ネットフィルターNetfilter は、パケット フィルタリング、転送、およびアドレス変換 NAT ...

JavaScript配列の重複排除のいくつかの方法についての詳細な説明

目次1.重複排除を設定する2. 重複を削除するには、2 回の for ループを使用します。 3. i...

Docker を使ってゼロから SOLO 個人ブログを構築する方法

目次1. 環境整備2. Dockerをインストールする3. MySQLマスタースレーブデータベースを...

Tomcatの動作原理を分析する

SpringBoot は巨大な Python のようで、ゆっくりと私たちの周りを巻きつき、麻痺させま...

Docker-Composeコマンドの使い方の詳しい説明

Docker コンテナはさまざまな方法で管理およびデプロイできます。 Docker コマンドを直接使...

ユーザー中心設計

最近、デジタル デザイン コミュニティで「誰が何を担当するのか」という明らかな混乱についてよく質問さ...

js ドラッグ アンド ドロップ テーブルでコンテンツ計算を実現する

この記事の例では、コンテンツの計算を実現するためのjsドラッグアンドドロップテーブルの具体的なコード...