ホームページ  >  記事  >  ウェブフロントエンド  >  Vue が MDV 効果を実現するためにデータをリアクティブにパッケージ化する方法を解釈する

Vue が MDV 効果を実現するためにデータをリアクティブにパッケージ化する方法を解釈する

WBOY
WBOY転載
2021-12-24 17:55:201583ブラウズ

この記事では、MDV (Model-Driven-View) の効果を実現するために vue がどのようにデータをリアクティブにパッケージ化するかについて説明します。

Vue が MDV 効果を実現するためにデータをリアクティブにパッケージ化する方法を解釈する まずリアクティブとは何かというと、簡単に言うと、データを観測可能な型にパッケージ化し、データの変化を感知できるようにすることです。

Vue の関連実装コードはすべて core/observer ディレクトリにありますので、自分で読みたい場合は core/instance/index から読むことをお勧めします.js

reactive の具体的な実装について話し始める前に、Watcher、Dep、Observer といういくつかのオブジェクトについて話しましょう。

Watcher

Watcher は、データを監視するために vue によって実装されたオブジェクトであり、具体的な実装は core/observer/watcher.js にあります。

このクラスは主に、メソッド/式 (データはリアクティブである必要がある、つまりデータまたはプロパティ) で参照されるデータの変更を監視し、それに応じて変更を処理するために使用されます。まず、Watcher クラスの入力パラメータを見てみましょう:

vm: Component,expOrFn: string | Function,cb: Function,options?: Object

これらの入力パラメータが何であるかを説明します:

  • vm: 現在の Watcher が属する VueComponent。
  • expOrFn: 監視する必要があるメソッド/式。例: VueComponent の render 関数、computed の getter メソッド、またはこのタイプの文字列 abc.bbc.aac (vue の parsePath メソッドは Split('.') 属性セグメンテーションを使用するため、abc[ 'bbc'] はサポートされていません)。 expOrFnがメソッドの場合はウォッチャーのgetter属性に直接代入され、式の場合はメソッドに変換されてgetterに渡されます。
  • cb: このコールバックは、ゲッターで参照されるデータが変更されるとトリガーされます。
  • オプション: 追加パラメータ。渡すことができるパラメータには、deepuserlazysync、これらの値のデフォルトは false です。
    • deep true の場合、ゲッターによって返されたオブジェクトは、さらに依存関係を収集するために再度詳細にトラバースされます。
    • user は、このリスナーが $watch を通じてユーザーによって呼び出されたかどうかをマークするために使用されます。
    • lazy は、ウォッチャーが遅延しているかどうかをマークするために使用されます。この属性は、計算されたデータに使用されます。データ内の値が変更された場合、新しい値を取得するためにゲッターはすぐには計算されませんが、ウォッチャーは計算されます。 dirty の場合は、新しい計算データを返すために計算データが参照される場合にのみ実行され、計算量が削減されます。
    • sync は、データの値が変更されたときにウォッチャーがデータを同期的に更新するかどうかを示します。true の場合、値はすぐに更新され、それ以外の場合は nextTick で更新されます。

#入力パラメータの用途を理解すると、基本的に Watcher オブジェクトが何を行うかがわかります。

Dep

Dep は依存関係を処理するために vue によって実装されたオブジェクトです。具体的な実装は core/observer/dep.js にあります。コードの量は非常に少ないです. わかりやすい。

Dep は主にリアクティブ データとウォッチャーを接続するリンクとして機能し、リアクティブ データが作成されるたびに dep インスタンスが作成されます。 Observer/index.js の defineReactive メソッドを参照してください。簡略化された defineReactive メソッドは次のとおりです。

function defineReactive(obj, key, value) {
    const dep = new Dep();
    Object.defineProperty(obj, key, {
        get() {
          if (Dep.target) {
            dep.depend();
          }
          return value        }
        set(newValue) {
            value = newValue;
            dep.notify();
        }
    })}

dep インスタンスの作成後、getter と setter のロジックがデータに挿入されます。データが参照されると、getter がトリガーされます。データはいつ参照されますか?これは、ウォッチャーがゲッターを実行するときであり、ウォッチャーがゲッターを実行すると、ウォッチャーは Dep.target に詰め込まれ、その後、dep.depend() メソッドを呼び出すことによって、このデータの dep との接続が作成されます。監視者。

接続の作成後、データが変更されると、セッター ロジックがトリガーされます。その後、dep.notify() を通じて dep に関連付けられたすべてのウォッチャーに通知できます。これにより、各ウォッチャーが応答できるようになります。

たとえば、データを監視し、計算されたデータ内の同じデータを参照します。同時に、テンプレート内でこのデータを明示的に参照したため、この時点で、このデータの dep は 3 つのウォッチャーに関連付けられています。1 つはレンダリング関数のウォッチャー、もう 1 つは計算のウォッチャー、もう 1 つは、$ を呼び出すユーザーです。watch メソッドによって作成されたウォッチャーです。データが変更されると、このデータの管理者はこれら 3 つのウォッチャーに、それに応じて処理するように通知します。

Observer

Observer は plainObject または配列をリアクティブに変えることができます。コードは非常に小さく、plainObject または配列を走査し、キー値ごとに defineReactive メソッドを呼び出すだけです。

プロセス

上記の 3 つのクラスを紹介すると、vue reactive の実装については基本的に漠然と理解できると思いますが、次に例を示しながらプロセス全体について説明します。

vue がインスタンス化されると、最初に initData が呼び出され、次に initComputed、最後に mountComponent が呼び出されて、レンダー関数のウォッチャーが作成されます。これで、VueComponent のデータ リアクティブが完了しました。

initData

initData 方法在 core/instance/state.js 中,而这个方法里大部分都是做一些判断,比如防止 data 里有跟 methods 里重复的命名之类的。核心其实就一行代码:

observe(data, true)

而这个 observe 方法干的事就是创建一个 Observer 对象,而 Observer 对象就像我上面说的,对 data 进行遍历,并且调用 defineReactive 方法。

就会使用 data 节点创建一个 Observer 对象,然后对 data 下的所有数据,依次进行 reactive 的处理,也就是调用 defineReactive 方法。当执行完 defineReactive 方法之后,data 里的每一个属性,都被注入了 getter 以及 setter 逻辑,并且创建了 dep 对象。至此 initData 执行完毕。

initComputed

然后是 initComputed 方法。这个方法就是处理 vue 中 computed 节点下的数据,遍历 computed 节点,获取 key 和 value,创建 watcher 对象,如果 value 是方法,实例化 watcher 的入参 expOrFn 则为 value,否则是 value.get。

function initComputed (vm: Component, computed: Object) {
  ...  const watchers = vm._computedWatchers = Object.create(null)  for (const key in computed) {
    const userDef = computed[key]    let getter = typeof userDef === 'function' ? userDef : userDef.get
    ...
    watchers[key] = new Watcher(vm, getter, noop, { lazy: true })    if (!(key in vm)) {
      defineComputed(vm, key, userDef)    } else if (process.env.NODE_ENV !== 'production') {
      ...    }
  }}

我们知道 expOrFn 是可以为方法,也可以是字符串的。因此,通过上面的代码我们发现了一种官方文档里没有说明的用法,比如我的 data 结构如下

{ obj: { list: [{value: '123'}] } }

如果我们要在 template 中需要使用 list 中第一个节点的 value 属性 值,就写个 computed:

computed: {
  value: { get: 'obj.list.0.value' }}

然后在 template 中使用的时候,直接用{<!-- -->{ value }},这样的话,就算 list 为空,也能保证不会报错,类似于 lodash.get 的用法。OK,扯远了,回到正题上。

创建完 watcher,就通过 Object.defineProperty 把 computed 的 key 挂载到 vm 上。并且在 getter 中添加以下逻辑

 if (watcher.dirty) {
   watcher.evaluate() }
 if (Dep.target) {
   watcher.depend() }
 return watcher.value

前面我有说过,computed data 的 watcher 是 lazy 的,当 computed data 中引用的 data 发生改变后,是不会立马重新计算值的,而只是标记一下 dirty 为 true,然后当这个 computed data 被引用的时候,上面的 getter 逻辑就会判断 watcher 是否为 dirty,如果是,就重新计算值。

而后面那一段watcher.depend。则是为了收集 computed data 中用到的 data 的依赖,从而能够实现当 computed data 中引用的 data 发生更改时,也能触发到 render function 的重新执行。

  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()    }
  }

mountComponent

把 data 以及 computed 都初始化好之后,则创建一个 render function 的 watcher。逻辑如下:

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean): Component {
  vm.$el = el
  ...  callHook(vm, 'beforeMount')  let updateComponent
  ...
    updateComponent = () => {
      vm._update(vm._render(), hydrating)    }
  ...  vm._watcher = new Watcher(vm, updateComponent, noop)  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')  }
  return vm}

可以看到,创建 watcher 时候的入参 expOrFn 为 updateComponent 方法,而 updateComponent 方法中则是执行了 render function。而这个 watcher 不是 lazy 的,因此创建该 watcher 的时候,就会立马执行 render function 了,当执行 render function 的时候。如果 template 中有使用 data,则会触发 data 的 getter 逻辑,然后执行 dep.depend() 进行依赖收集,如果 template 中有使用 computed 的参数,也会触发 computed 的 getter 逻辑,从而再收集 computed 的方法中引用的 data 的依赖。最终完成全部依赖的收集。

最后举个例子:

<template>
    <p>{{ test }}</p></template><script>
  export default {
    data() {
      return {
        name: 'cool'
      }
    },
    computed: {
      test() {
        return this.name + 'test';
      }
    }
  }</script>

初始化流程:

  1. 将 name 处理为 reactive,创建 dep 实例
  2. 将 test 绑到 vm,创建 test 的 watcher 实例 watch1,添加 getter 逻辑。
  3. 创建 render function 的 watcher 实例 watcher2,并且立即执行 render function。
  4. 执行 render function 的时候,触发到 test 的 getter 逻辑,watcher1 及 watcher2 均与 dep 创建映射关系。

name 的值变更后的更新流程:

  1. 遍历绑定的 watcher 列表,执行 watcher.update()。
  2. watcher1.dirty 置为为 true。
  3. watcher2 重新执行 render function,触发到 test 的 getter,因为 watcher1.dirty 为 true,因此重新计算 test 的值,test 的值更新。
  4. 重渲染 view

【相关推荐:《vue.js教程》】

以上がVue が MDV 効果を実現するためにデータをリアクティブにパッケージ化する方法を解釈するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。