この記事では、MDV (Model-Driven-View) の効果を実現するために vue がどのようにデータをリアクティブにパッケージ化するかについて説明します。
まずリアクティブとは何かというと、簡単に言うと、データを観測可能な型にパッケージ化し、データの変化を感知できるようにすることです。
Vue の関連実装コードはすべて core/observer
ディレクトリにありますので、自分で読みたい場合は core/instance/index から読むことをお勧めします.js
。
reactive の具体的な実装について話し始める前に、Watcher、Dep、Observer といういくつかのオブジェクトについて話しましょう。
Watcher は、データを監視するために vue によって実装されたオブジェクトであり、具体的な実装は core/observer/watcher.js
にあります。
このクラスは主に、メソッド/式
(データはリアクティブである必要がある、つまりデータまたはプロパティ) で参照されるデータの変更を監視し、それに応じて変更を処理するために使用されます。まず、Watcher クラスの入力パラメータを見てみましょう:
vm: Component,expOrFn: string | Function,cb: Function,options?: Object
これらの入力パラメータが何であるかを説明します:
abc.bbc.aac
(vue の parsePath メソッドは Split('.') 属性セグメンテーションを使用するため、abc[ 'bbc']
はサポートされていません)。 expOrFnがメソッドの場合はウォッチャーのgetter属性に直接代入され、式の場合はメソッドに変換されてgetterに渡されます。 deep
、user
、lazy
、sync
、これらの値のデフォルトは false です。 dirty
の場合は、新しい計算データを返すために計算データが参照される場合にのみ実行され、計算量が削減されます。 #入力パラメータの用途を理解すると、基本的に Watcher オブジェクトが何を行うかがわかります。
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 は plainObject または配列をリアクティブに変えることができます。コードは非常に小さく、plainObject または配列を走査し、キー値ごとに defineReactive
メソッドを呼び出すだけです。
上記の 3 つのクラスを紹介すると、vue reactive の実装については基本的に漠然と理解できると思いますが、次に例を示しながらプロセス全体について説明します。
vue がインスタンス化されると、最初に initData が呼び出され、次に initComputed、最後に mountComponent が呼び出されて、レンダー関数のウォッチャーが作成されます。これで、VueComponent のデータ リアクティブが完了しました。
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 方法。这个方法就是处理 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() } }
把 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>
【相关推荐:《vue.js教程》】
以上がVue が MDV 効果を実現するためにデータをリアクティブにパッケージ化する方法を解釈するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。