Analysis of Vue3 response principle

How does the responsive module work in Vue3? For example, there are three attributes: price, quantity and total price.

let price = 5
let quantity = 2
let total = price * quantity

We want to be responsive, that is, when the price is updated, the price on the web page is updated, and the calculated total is also updated. Define a calculation function:

function getTotal (price, quantity){
  let total = price * quantity 
}

We want to execute the getTotal function again after updating the price, so that we can get a responsive total. So how does vue3 implement this process?

In Vue3, a variety of data structures are used to execute the whole mechanism, such as:

  • Define track to save this function code.
  • Define the effect to execute the code.
  • Define a trigger to execute all stored code.

First, write the above getTotal to the effect as an anonymous function:

let effect = () => {
    total = price * quantity
}

Use a set dep to save all effect s

let dep = new Set()

track as a function, add effect to the set dep:

function track() {
    dep.add(effct)
}

trigger is used as a function to traverse all effect s in dep and execute them one by one.

function trigger() {
    dep.forEach( effct => effct() )
}

1, Single responsive object

Generally speaking, an object has multiple properties, such as price and quantity mentioned above.

let product = { price : 5, quantity : 2  }

We want each property to have its own dep, so that no matter which property is modified, total can become "responsive".

You can define a dep map (ES6 map). The key of the map is the name of the attribute, such as price or quantity. The value of a map is a DEP, that is, a set of effect s.

The track function and trigger function need to be rewritten as:

const depMap = new Map()
function track(key) {
    let dep = depMap.get(key)   // obtain property of dep
    if(!dep) {
        depMap.set(key, (dep = new Set()))   //If it doesn't exist dep,Then create a
    }
    dep.add(effect)   // Add to collection effect
}
function trigger(key) {
    let dep = depMap.get(key)   // obtain property of dep
    if(dep) {                
        dep.forEach(effect => {   // dep If it exists, traverse dep All in effect
            effect()
        })
    }
}

Now summarize all the code and let's see if the response works

let product = { price : 5, quantity : 2 }
let total = 0
let effect = () => {
    total = product.price * product.quantity
}
const depMap = new Map()
function track(key) {
    let dep = depMap.get(key)   // obtain property of dep
    if(!dep) {
        depMap.set(key, (dep = new Set()))   //If it doesn't exist dep,Then create a
    }
    dep.add(effect)   // Add to collection effect
}
function trigger(key) {
    let dep = depMap.get(key)   // obtain property of dep
    if(dep) {                
        dep.forEach(effect => {   // dep If it exists, traverse dep All in effect
            effect()
        })
    }
}
track('quantity')
effect()
console.log(total)   //10

product.quantity = 3
trigger('quantity')
console.log(total)    //15

Copy this code to the console and test the run results. Now we have a responsive object product. If you modify any property of product, as long as you call the above method, the total will change.

2, Multiple responsive objects

However, the dep map in the above code only stores the attributes and DEPs of a single object product. What if we need multiple responsive objects now? For example, a user object:

let user = {
    name : 'LUJT',
    age  : 19
}

Since a depMap can only handle one responsive object, if there are multiple objects, an obvious idea is to define a Map, where key is an object and value is a depMap. Here we use WeakMap. The definition of WeakMap in MDN is that a WeakMap object is a set of key / value pairs, in which the key is weakly referenced. The key must be an object, and the value can be arbitrary.

It meets our needs very much! Therefore, we define a WeakMap as follows:

const targetMap = new WeakMap

Our core code can be rewritten as:

const targetMap = new WeakMap   //targetMap Used to save objects and their depMap
function track(target, key) {
    let depMap = targetMap.get(target)   // Gets the name of the object depMap
       if(!depMap) {
        targetMap.set(target, (depMap = new Map()))   //If it doesn't exist depMap,Then create a
    }
    let dep = depMap.get(key)   // obtain property of dep
    
    if(!dep) {
        depMap.set(key, (dep = new Set()))   //If it doesn't exist dep,Then create a
    }
    
    dep.add(effect)   // Add to collection effect
}
function trigger(target, key) {
    const depMap = targetMap.get(target)
    if(!depMap){return}
    
    let dep = depMap.get(key)   // obtain property of dep
    if(dep) {                
        dep.forEach(effect => {   // dep If it exists, traverse dep All in effect
            effect()
        })
    }
}

In this way, the reactive work of setting up multiple objects is completed.

Vue3 uses effect function to store dynamically changing execution conditions, dep set to store multiple effects, depMap to store object attributes and dep, and targetMap to store object and depMap.

Posted by jjfletch on Fri, 29 Oct 2021 07:13:06 -0700