Table of Contents
Watcher
Dep
Observer
Process
initData
initComputed
mountComponent
初始化流程:
name 的值变更后的更新流程:
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

Dec 24, 2021 pm 05:55 PM
vue

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!

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

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

Java Tutorial
1662
14
PHP Tutorial
1261
29
C# Tutorial
1234
24
How to use bootstrap in vue How to use bootstrap in vue Apr 07, 2025 pm 11:33 PM

Using Bootstrap in Vue.js is divided into five steps: Install Bootstrap. Import Bootstrap in main.js. Use the Bootstrap component directly in the template. Optional: Custom style. Optional: Use plug-ins.

How to add functions to buttons for vue How to add functions to buttons for vue Apr 08, 2025 am 08:51 AM

You can add a function to the Vue button by binding the button in the HTML template to a method. Define the method and write function logic in the Vue instance.

How to use watch in vue How to use watch in vue Apr 07, 2025 pm 11:36 PM

The watch option in Vue.js allows developers to listen for changes in specific data. When the data changes, watch triggers a callback function to perform update views or other tasks. Its configuration options include immediate, which specifies whether to execute a callback immediately, and deep, which specifies whether to recursively listen to changes to objects or arrays.

What does vue multi-page development mean? What does vue multi-page development mean? Apr 07, 2025 pm 11:57 PM

Vue multi-page development is a way to build applications using the Vue.js framework, where the application is divided into separate pages: Code Maintenance: Splitting the application into multiple pages can make the code easier to manage and maintain. Modularity: Each page can be used as a separate module for easy reuse and replacement. Simple routing: Navigation between pages can be managed through simple routing configuration. SEO Optimization: Each page has its own URL, which helps SEO.

How to return to previous page by vue How to return to previous page by vue Apr 07, 2025 pm 11:30 PM

Vue.js has four methods to return to the previous page: $router.go(-1)$router.back() uses &lt;router-link to=&quot;/&quot; component window.history.back(), and the method selection depends on the scene.

React vs. Vue: Which Framework Does Netflix Use? React vs. Vue: Which Framework Does Netflix Use? Apr 14, 2025 am 12:19 AM

Netflixusesacustomframeworkcalled"Gibbon"builtonReact,notReactorVuedirectly.1)TeamExperience:Choosebasedonfamiliarity.2)ProjectComplexity:Vueforsimplerprojects,Reactforcomplexones.3)CustomizationNeeds:Reactoffersmoreflexibility.4)Ecosystema

How to use vue traversal How to use vue traversal Apr 07, 2025 pm 11:48 PM

There are three common methods for Vue.js to traverse arrays and objects: the v-for directive is used to traverse each element and render templates; the v-bind directive can be used with v-for to dynamically set attribute values ​​for each element; and the .map method can convert array elements into new arrays.

How to jump to the div of vue How to jump to the div of vue Apr 08, 2025 am 09:18 AM

There are two ways to jump div elements in Vue: use Vue Router and add router-link component. Add the @click event listener and call this.$router.push() method to jump.

See all articles