Vueデータ変更検出の基本的な実装の簡単な分析

Vueデータ変更検出の基本的な実装の簡単な分析

1. オブジェクトの変更検出

次に、データの変更を検出するロジックをシミュレートします。

ここで、これから行うことを強調しておきます。データの変更を外部に通知します (その後、外部はビューの再レンダリングなど、独自の論理処理を実行します)。

コーディングを始める前に、まず次の質問に答える必要があります。

1. オブジェクトの変化を検出するにはどうすればよいでしょうか?

  • Object.defineProperty() を使用します。データの読み取り時にゲッターがトリガーされ、データの変更時にセッターがトリガーされます。
  • オブジェクトの変更を検出することによってのみ、データが変更されたときに通知を発行できます。

2. データが変更された場合、誰に通知しますか?

  • データが使用される場所を通知します。データはテンプレートまたは vm.$watch() で使用できます。場所によって動作が異なります。たとえば、テンプレートをここでレンダリングする必要があり、他のロジックをそこで実行する必要があります。したがって、クラスを抽象化するだけです。データが変更されたら通知し、他の場所にも通知します。
  • このクラスの名前は Watcher です。それは単なる仲介者です。

3. 誰に頼ればいいのか?

  • 誰に通知するかは、あなた、つまりウォッチャー次第です。

4. いつ通知されますか?

  • データを変更する場合。つまり、セッターの通知

5. 依存関係をいつ収集しますか?

  • データが使用される場所を通知する必要があるためです。データを使用するには、データを読み取る必要があります。データを読み取るときに、つまりゲッターで収集することで、データを収集できます。

6.データはどこで収集されますか?

  • 各属性に配列を定義し、その属性に関連するすべての依存関係をその中に配置することができます。

コードは次のとおりです (直接実行できます)。

// 依存関係を保存するために使用されるグローバル変数 let globalData = undefined;

// データをレスポンシブに変換する function defineReactive (obj, key, val) {
    // 依存関係リスト letdependList = []
    Object.defineProperty(obj, キー, {
      列挙可能: true、
      設定可能: true、
      取得: 関数 () {
        // 依存関係を収集する (ウォッチャー)
        グローバルデータ && 依存リスト.push(グローバルデータ)
        戻り値
      },
      設定: 関数 reactiveSetter (newVal) {
        if(val === newVal){
            戻る
        }
        // 通知の依存関係 (ウォッチャー)
        依存リスト.forEach(w => {
            w.update(新しい値、val)
        })
        val = 新しい値
      }
    });
}

// クラス Watcher に依存{
    コンストラクタ(データ、キー、コールバック){
        this.data = データ;
        this.key = キー;
        this.callback = コールバック;
        this.val = this.get();
    }
    // このコードは依存関係リストに自身を追加できます get(){
        // 依存関係を globalData に保存する
        グローバルデータ = これ;
        // データを読み取るときに依存関係を収集します let value = this.data[this.key]
        グローバルデータ = 未定義
        戻り値;
    }
    // データが変更されたときに通知を受け取り、外部に通知します update(newVal, oldVal){
        this.callback(新しいVal、古いVal)
    }
}

/* 以下はテストコードです */
データを {} とします。
// name 属性をレスポンシブにする defineReactive(data, 'age', '88')
// データの経過時間が変わると、ウォッチャーに通知され、ウォッチャーは外部に通知します。new Watcher(data, 'age', (newVal, oldVal) => {
    console.log(`外の世界: newVal = ${newVal}; oldVal = ${oldVal}`)
})

data.age -= 1 // コンソール出力: 外部: newVal = 87; oldVal = 88

コンソールでdata.age -= 1を実行し続けると、外界:newVal = 86 ; oldVal = 87

添付されているのは、Data、defineReactive、dependList、Watcher と外部世界との関係図です。

まず、defineReactive() メソッド ( defineReactive(data, 'age', '88') ) を使用してデータをレスポンシブに変換します。

外部はWatcher( let value = this.data[this.key] )を介してデータを読み取り、データゲッターがトリガーされ、その後、globalDataを介してWatcherが収集されます。

データが変更されると ( data.age -= 1 )、セッターがトリガーされ、依存関係 (dependList) に通知され、依存関係は Watcher に通知し ( w.update(newVal, val) )、最後に Watcher が外部に通知します。

2. オブジェクトに関する質問

考えてみてください。上記の例では、 delete data.age続行すると外部に通知されるでしょうか?

しません。セッターがトリガーされないためです。以下をお読みください:

<!DOCTYPE html>
<html lang="ja">
<ヘッド>
    <メタ文字セット="UTF-8">
    <meta name="viewport" content="width=デバイス幅、初期スケール=1.0">
    <title>ドキュメント</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<本文>
    <div id='アプリ'>
        <セクション>
            {{ p1.name }}
            {{ p1.age }}
        </セクション>
    </div>
<スクリプト>
constアプリ = 新しいVue({
    el: '#app',
    データ: {
        p1: {
            名前: 'ph',
            年齢: 18
        }
    }
})
</スクリプト>
</本文>
</html>

実行後、ページにph 18表示されます。データが変更されるとビューが再レンダリングされることがわかっているので、コンソールでdelete app.p1.nameを実行し、ページが変更されていないことを確認します。これは、上記の例でdelete data.ageを実行するのと同じです。セッターはトリガーされず、外部に通知されません。

この問題を解決するために、Vue は vm.$set と vm.$delete という 2 つの API (後で紹介します) を提供します。

app.$delete(app.p1, 'age')実行を続けると、ページに情報がないことがわかります (name 属性は delete によって削除されましたが、その時点では再レンダリングされていません)。

注意: ここでapp.p1.sex = 'man'を実行すると、データ p1 が使用されている場所が通知されません。この問題は vm.$set によって解決できます。

配列変更検出

3.1 背景

もし、 let data = {a:1, b:[11, 22]}場合、Object.defineProperty でレスポンシブ型に変換した後、 data.a = 2というデータに変更を加え、外部に通知します。これは分かりやすいですね。同様に、 data.b = [11, 22, 33]も外部に通知します。しかし、 data.b.push(33)のように別の方法でデータ b を変更した場合は、セッターが使われていないため、外部に通知されません。例をご覧ください:

関数defineReactive(obj, key, val) {
    Object.defineProperty(obj, キー, {
      列挙可能: true、
      設定可能: true、
      取得: 関数 () {
        console.log(`get val = ${val}`)
        戻り値
      },
      設定: 関数 reactiveSetter (newVal) {
        if(val === newVal){
            戻る
        }
        console.log(`set val = ${newVal}; oldVal = ${val}`)
        val = 新しい値
      }
    });
}

// 以下はテストコードです {1}
データを {} とします
defineReactive(データ, 'a', [11,22])
data.a.push(33) // get val = 11,22 (setterはトリガーされません) {2}     
data.a // 値 = 11,22,33 を取得 
data.a = 1 // set val = 1; oldVal = 11,22,33 (トリガー セッター)

push() メソッドを介して配列の値を変更しても、セッター (行 {2}) はトリガーされないため、外部に通知することはできません。これは問題を示しているようです。Object.definePropery() メソッドでは、オブジェクトのみをレスポンシブに変換できますが、配列はレスポンシブに変換できません。

実際、Object.definePropery() は配列をレスポンシブな配列に変換できます。例をご覧ください:

// 上記の例を続けて、テスト コード (行 {1}) を次のように変更します。
データ = []
リアクティブを定義します(データ、'0'、11)
data[0] = 22 // val = 22; oldVal = 11 に設定
data.push(33) // {10} はトリガーされません

Object.definePropery() は配列をレスポンシブにすることができますが、 data.push(33) (行 {10}) を介して配列を変更しても、外部に通知されません。

したがって、Vue では、データを応答性に変換するために 2 セットのメソッドが使用されます。オブジェクトは Object.defineProperty() を使用し、配列は別のセットを使用します。

3.2 実装

es6 では、Proxy を使用して配列の変更を検出できます。例をご覧ください:

データ = [11,22]とする
p = new Proxy(data, {
    設定: 関数(ターゲット、プロパティ、値、レシーバー) {
        ターゲット[prop] = 値;
        console.log('プロパティセット: ' + prop + ' = ' + 値);
        true を返します。
    }
    })
コンソールログ(p)
p.push(33)
/*
出力:
[ 11 、 22 ]
プロパティセット: 2 = 33
プロパティセット: 長さ = 3
*/

es6 より前は少し面倒でしたが、インターセプターを使うことができました。原則としては、 [].push()を実行すると、配列プロトタイプ (Array.prototype) 内のメソッドが呼び出されます。 [].push()Array.prototype将来[].push()が呼び出されると、インターセプター内の push() メソッドが最初に実行され、インターセプター内の push() メソッドが Array.prototype 内の push() メソッドを呼び出します。例をご覧ください:

// 配列のプロトタイプ let arrayPrototype = Array.prototype

// インターセプターを作成する let interceptor = Object.create(arrayPrototype)

// インターセプターを元の配列のメソッドに関連付けます。('push,pop,unshift,shift,splice,sort,reverse').split(',')
.forEach(メソッド => {
    origin = arrayPrototype[メソッド]とします。
    Object.defineProperty(インターセプター、メソッド、{
        値: 関数(...引数){
            console.log(`インターセプター: args = ${args}`)
            origin.apply(this, args); を返します。
        },
        列挙可能: false、
        書き込み可能: true、
        設定可能: true
    })
});

// テスト let arr1 = ['a']
arr2 = [10]とする
arr1.push('b')
// 配列 arr2 の変更を検出する Object.setPrototypeOf(arr2, interceptor) // {20}
arr2.push(11) // インターセプター: args = 11
arr2.unshift(22) // インターセプター: args = 22

この例では、配列自体の内容を変更できる 7 つのメソッドをインターセプターに追加します。配列の変更を検出する必要がある場合は、配列のプロトタイプをインターセプターにポイントします (行 {20})。 push などの 7 つのメソッドを通じて配列を変更すると、インターセプターでトリガーされ、外部に通知できるようになります。

この時点では、配列の変更を検出するタスクのみが完了しています。

データが変更されたら、外部に通知します。上記のエンコーディング実装はオブジェクト データ専用ですが、ここでは配列データ用に実装する必要があります。

同じ質問について考えてみましょう:

1. 配列の変更を検出するにはどうすればよいでしょうか?

  • インターセプター

2. データが変更された場合、誰に通知しますか?

  • ウォッチャー

3. 誰に頼ればいいのか?

  • ウォッチャー

4. いつ通知されますか?

  • データを変更する場合。インターセプター内の通知。

5. 依存関係をいつ収集しますか?

  • データが使用される場所を通知する必要があるためです。データを使用するには、データを読み取る必要があります。データの読み取り時に収集されます。これはオブジェクト コレクションの依存関係と同じです。
  • {a: [11,22]}たとえば、配列 a を使用する場合は、オブジェクトの属性 a にアクセスする必要があります。

6.データはどこで収集されますか?

  • オブジェクトは各属性の依存関係を収集しますが、ここでは配列がインターセプターの依存関係をトリガーする可能性があることを考慮する必要があり、位置を調整する必要がある場合があります。

以上です。ここでは詳細には触れません。次の記事では、Vue におけるデータ検出に関するソースコードを抽出し、この記事と合わせて簡単に分析してみたいと思います。

IV. 配列に関する質問

// vue.js を自分でインポートする必要があります。今後はコアコードのみをリストするようにします <div id='app'>
        <セクション>
            {{p1[0]}}
            {{ p1[1] }}
        </セクション>
</div>
<スクリプト>
constアプリ = 新しいVue({
    el: '#app',
    データ: {
        p1: ['ph', '18']
    }
})
</スクリプト>

実行後、ページにph 18が表示されます。コンソールはapp.p1[0] = 'lj'を実行し、ページは応答しません。これは、配列が指定された 7 つのメソッドを呼び出してインターセプターを介して外部に通知することしかできないためです。 app.$set(app.p1, 0, 'pm')ページの内容はpm 18になります。

上記は、vue 検出データ変更の基本的な実装の簡単な分析です。vue 検出データ変更の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Vueはタブを切り替えてデータの状態を維持する3つの方法を実装します
  • Vue コンポーネント値転送中のデータ損失の分析と解決
  • vue+echarts によるデータ可視化の大画面表示の実装
  • Echarts をベースにした Vue でのドラッグデータ可視化機能の実装
  • Vueはデータを取得しますが、ページ上にレンダリングできません
  • Antd-vue テーブルコンポーネントは、データ行をクリックするためのクリックイベントを追加します。チュートリアル
  • vue+echarts+datav 大画面データ表示と中国地図の省、市、郡のドリルダウン機能の実装
  • vuex で遭遇する落とし穴、vuex データの変更、コンポーネント内のページ レンダリング操作
  • Vueは2つのコンポーネント間のデータ共有と変更操作を実装します

<<:  CentOS8 Linux 8.0.1905 のインストール手順(図解)

>>:  MySQLのクラスタモードでのgalera-clusterのデプロイメントの詳細説明

推薦する

VMware、nmap、burpsuite インストール チュートリアル

目次VMware バープスイート1. 仮想マシンイメージとVMwareのインストールと使用2. 仮想...

count(1)、count(*)、count(列名)の実行の違いの詳細な説明

実施効果: 1. count(1) と count(*)テーブル内のデータ量が多い場合、テーブルを分...

Vue の新しい組み込みコンポーネントの使用方法の詳細な説明

目次1. テレポート1.1 テレポートの紹介1.2 テレポートの使用1.3 プレビュー効果2. サス...

HTMLはWEB標準の開発の中心的な基盤です

HTML 中心のフロントエンド開発は、ほぼ Web 標準の意味です。共通しているのは「分離」という考...

シンプルなカレンダー効果を実現する js

この記事では、シンプルなカレンダー効果を実現するためのjsの具体的なコードを参考までに共有します。具...

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

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

CSSは複数の要素をボックスの両端に揃える効果を実現します

要素の両端を揃える配置レイアウトは、実際の開発のいたるところで見られます。これは、フレックスレイアウ...

クエリでのMySQLのユニークキーの使用と関連する問題

1. テーブルステートメントを作成します。 テーブル「従業員」を作成します( `emp_no` in...

Kali Linux Vmware 仮想マシンのインストール (図とテキスト)

準備: 1. VMwareワークステーションソフトウェアをインストールする2. Kali Linux...

Dockerfile を使用して Node.js サービスをデプロイする方法

Dockerfileを初期化するプロジェクトの名前が express であると仮定して、expres...

MySQLファイルストレージの詳細な説明

ファイルシステムとは何かInnoDB や MyIASM などのストレージ エンジンはテーブルをディス...

ポップアップウィンドウの上下中央左右と透明な背景のロックウィンドウ効果を実現する CSS

クリック後にポップアップボックスを実現し、上下左右に中央揃えし、灰色の透明マスクを追加してウィンドウ...

MySQLデータベースの圧縮バージョンのインストールと設定に関する詳細なチュートリアル

目次1. MySQLをダウンロードする2. 圧縮パッケージを解凍する3. MySQLを初期化する4....

Nginx フォワード プロキシとリバース プロキシ、および負荷分散機能の構成コード例

この記事は主に、Nginx のフォワード プロキシとリバース プロキシ、および負荷分散機能の設定コー...

Windows での Apache+Tomcat7 負荷分散構成方法の詳細な説明

準備Windows Server 2008 R2 Enterprise (2.40GH、8GB、64...