This article will talk about how vue packages data into reactive to achieve the effect of MDV (Model-Driven-View). I hope it will be helpful to everyone.
Let me first explain what reactive is. Simply put, it is to package data into an observable type. When the data changes, we can sense it.
The relevant implementation codes of Vue are all in the core/observer
directory. If you want to read it by yourself, it is recommended to start from core/instance/index.js
.
Before we start talking about the specific implementation of reactive, let’s talk about a few objects: Watcher, Dep, and Observer.
Watcher is an object implemented by vue for observing data. The specific implementation is in core/observer/watcher.js
.
This class is mainly used to observe changes in the data referenced in methods/expressions
(the data needs to be reactive, that is, data or props), and handle the changes accordingly. Let’s first take a look at the input parameters of the Watcher class:
vm: Component,expOrFn: string | Function,cb: Function,options?: Object
Explain what these input parameters are for:
abc.bbc.aac
(because vue's parsePath method uses split('.') attribute segmentation, so abc['bbc']
is not supported). If expOrFn is a method, it will be directly assigned to the getter attribute of watcher. If it is an expression, it will be converted into a method and then given to the getter. deep
, user
, lazy
, sync
, These values default to false. dirty
, it will only be executed when the computed data is referenced to return new computed data, thereby reducing the amount of calculation. #After understanding what the input parameters are used for, you basically know what the Watcher object does.
Dep is an object implemented by vue to handle dependencies. The specific implementation is in core/observer/dep.js
. The amount of code is quite small. Easy to understand.
Dep mainly serves as a link, connecting reactive data and watcher. Every creation of reactive data will create a dep instance. See the defineReactive
method in observer/index.js. The simplified defineReactive method is as follows.
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(); } })}
After the dep instance is created, the logic of getter and setter will be injected into the data. When the data is referenced, the getter will be triggered. When will the data be referenced? That is when the watcher executes the getter, and when the watcher executes the getter, the watcher will be stuffed into Dep.target, and then by calling the dep.depend() method, the dep of this data will create a connection with the watcher.
After the connection is created, when data is changed, the setter logic is triggered. Then you can notify all watchers associated with dep through dep.notify(). This allows each watcher to respond.
For example, I watch a data and reference the same data in a computed data. At the same time, I also explicitly referenced this data in the template, so at this time, the dep of this data is associated with three watchers, one is the watcher of the render function, the other is the watcher of computed, and the other is the user calling $ The watcher created by the watch method. When data changes, the dep of this data will notify these three watchers to handle it accordingly.
Observer can turn a plainObject or array into reactive. The code is very small, it just traverses the plainObject or array and calls the defineReactive
method for each key value.
After introducing the above three classes, you should basically have a vague understanding of the implementation of vue reactive. Next, let’s talk about the entire process with examples.
When vue is instantiated, initData will be called first, then initComputed, and finally mountComponent will be called to create the watcher of the render function. This completes the data reactive of a 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教程》】
The above is the detailed content of Interpret how Vue packages data into reactive to achieve MDV effect. For more information, please follow other related articles on the PHP Chinese website!