Home > Web Front-end > Vue.js > Interpret how Vue packages data into reactive to achieve MDV effect

Interpret how Vue packages data into reactive to achieve MDV effect

WBOY
Release: 2021-12-24 17:55:20
forward
1601 people have browsed it

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.

Interpret how Vue packages data into reactive to achieve MDV effect 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

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
Copy after login

Explain what these input parameters are for:

  • vm: The VueComponent to which the current watcher belongs.
  • expOrFn: Method/expression that needs to be monitored. For example: VueComponent's render function, or computed's getter method, or a string of this type 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.
  • cb: This callback will be triggered when the data referenced in the getter changes.
  • options: Additional parameters, the parameters that can be passed include deep, user, lazy, sync, These values ​​default to false.
    • deep If true, the object returned by the getter will be traversed in depth again for further dependency collection.
    • user is used to mark whether this listener is called by the user through $watch.
    • lazy is used to mark whether the watcher is lazy. This attribute is used for computed data. When the value in the data changes, the getter will not be calculated immediately to obtain the new value, but the watcher will be marked. For dirty, it will only be executed when the computed data is referenced to return new computed data, thereby reducing the amount of calculation.
    • sync indicates whether the watcher updates the data synchronously when the value in data changes. If it is true, the value will be updated immediately, otherwise it will be updated in nextTick.

#After understanding what the input parameters are used for, you basically know what the Watcher object does.

Dep

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();
        }
    })}
Copy after login

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

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.

Process

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

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

observe(data, true)
Copy after login

而这个 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') {
      ...    }
  }}
Copy after login

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

{ obj: { list: [{value: '123'}] } }
Copy after login

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

computed: {
  value: { get: 'obj.list.0.value' }}
Copy after login

然后在 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
Copy after login

前面我有说过,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()    }
  }
Copy after login

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}
Copy after login

可以看到,创建 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>
Copy after login

初始化流程:

  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教程》】

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!

Related labels:
vue
source:csdn.net
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template