Before we look at vue3’s responsivity principle, let’s review some disadvantages of VUE2’s responsivity principle:

  • The reactivity process requires recursive traversal and consumes a lot of energy
  • New or deleted attributes cannot be listened on
  • Array responsivity requires additional implementation
  • The syntax for unresponsive modification of Map, Set, and Class is limited

Vue3.0 uses ES6’s Proxy feature to solve these problems. Here we use Proxy to implement a responsive function:

function reactive(obj) {
    // The Proxy can accept only one object
    if (typeofobj ! = ='object'&& obj ! =null) {
        return obj
    }
    // Proxy adds interception to an object
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            // Reflect is used to perform the default action on objects, which is more formal and friendlier
            // Both Proxy and Object methods Reflect
            const res = Reflect.get(target, key, receiver)
            console.log(` access${key}:${res}`)
            return res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            console.log(` set${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(` delete${key}:${res}`)
            return res
        }
    })
    return observed
}

Copy the code

The test code

const state = reactive({
    name: 'Joe'.hobbies: { book: 'programming' }
})
state.name  // Get name: zhang SAN
state.name = 'bill' // set name: li Si
state.age = 29 / / set the age: 29
delete state.age / / remove the age: true
state.hobbies.book // Get hobbies:[object object]
Copy the code

Reactive can’t get values correctly if there are other nested objects in the reactive method.

Nested object responsive

// Define a utility method to determine if the value of get is an object.
const isObject = val= >val ! = =null && typeof val === 'object'

function reactive(obj) {
    // The Proxy can accept only one object
    if(! isObject(obj)) {return obj 
	}
    // Proxy adds interception to an object
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            // Reflect is used to perform the default action on objects, which is more formal and friendlier
            // Both Proxy and Object methods Reflect
            const res = Reflect.get(target, key, receiver)
             console.log(` access${key}:${res}`)
            // Check whether the value of get is an object, if so, recurse
            return isObject(res) ? reactive(res) : res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            console.log(` set${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(` delete${key}:${res}`)
            return res
        }
    })
    return observed
}

// Test the code
const state = reactive({
    name: 'Joe'.hobbies: { book: 'programming' }
})

state.hobbies.book  // Get book: programming

Copy the code

Note: Reactive functions in Vue3.0 are much more complex. They deal with duplicate agents, new or modified values, and new and old values.

The problem that nested objects cannot trigger get has been solved. Let’s establish the correspondence between the response data and the update function.

Depend on the collection

Establish the correspondence between the response data key and the update function.

// When the user modifies the associated data, the response function is triggered
const state = reactive({name:'Joe'})
state.name = 'bill'
// Set the response function, which is updated when state.name changes.
effect(() = > console.log(state.foo))
Copy the code

To do this, let’s implement three functions:

  • Effect: Saves the callback function for later use, and immediately executes the callback function to trigger the getter for some of the response data in it
  • Track: Call track in the getter to map the previously stored callback function to the current target and key
  • Trigger: Trigger is called in the setter, and the target and key response functions are executed

Create effect function **

// Save the current active response function as a bridge between the getter and effect
const effectStack = []
// effect task: execute fn and push it onto the stack
function effect(fn) {
    const rxEffect = function () { // 1. Catch possible exceptions
        try {
            // 2. Push for subsequent dependency collection
            effectStack.push(rxEffect)
            // 3. Run fn to trigger dependency collection
            return fn()
        } finally {
            // 4. The execution is complete and the stack is removed
            effectStack.pop()
        }
    }
    // The response function is executed once by default
    rxEffect()
    // Return the response function
    return rxEffect
}
Copy the code

** 2, track, trigger methods to achieve **

// Create a mapping table.
// {target: {key: [fn1,fn2]}} 
let targetMap = new WeakMap(a)function track(target, key) {
    // Remove the response function from the stack
    const effect = effectStack[effectStack.length - 1]
    if (effect) {
        // Get the dependency table for target
        let depsMap = targetMap.get(target)
        if(! depsMap) { depsMap =new Map()
            targetMap.set(target, depsMap)
        }
        // Get the set of response functions corresponding to key
        let deps = depsMap.get(key)
        if(! deps) { deps =new Set()
            depsMap.set(key, deps)
        }
        if(! deps.has(effect)) { deps.add(effect) } } }// Trigger the target.key response function
function trigger(target, key) {
    // Get the dependency table
    const depsMap = targetMap.get(target)
    if (depsMap) {
        // Get the set of response functions
        const deps = depsMap.get(key)
        if (deps) {
            // Execute all response functions
            deps.forEach(effect= > {
                effect()
            })
        }
    }
}

Copy the code

After the Proxy method is implemented, we only need to collect the dependencies in the get and set constructor. Here is the complete code:

const isObject = val= >val ! = =null && typeof val === 'object'

function reactive(obj) {
    // The Proxy can accept only one object
    if(! isObject(obj)) {return obj
    }
    // Proxy adds interception to an object
    const observed = new Proxy(obj, {
        get(target, key, receiver) {
            // Reflect is used to perform the default action on objects, which is more formal and friendlier
            // Both Proxy and Object methods Reflect
            const res = Reflect.get(target, key, receiver)
            track(target, key)
            console.log(` access${key}:${res}`)
            // Check whether the value of get is an object, if so, recurse
            return isObject(res) ? reactive(res) : res
        },
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            trigger(target, key)
            console.log(` set${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(` delete${key}:${res}`)
            return res
        }
    })
    return observed
}
// Save the current active response function as a bridge between the getter and effect
const effectStack = []
// effect task: execute fn and push it onto the stack
function effect(fn) {
    const rxEffect = function () { // 1. Catch possible exceptions
        try {
            // 2. Push for subsequent dependency collection
            effectStack.push(rxEffect)
            // 3. Run fn to trigger dependency collection
            return fn()
        } finally {
            // 4. The execution is complete and the stack is removed
            effectStack.pop()
        }
    }
    // The response function is executed once by default
    rxEffect()
    // Return the response function
    return rxEffect
}

// Create a mapping table.
// {target: {key: [fn1,fn2]}} 
let targetMap = new WeakMap(a)function track(target, key) {
    // Remove the response function from the stack
    const effect = effectStack[effectStack.length - 1]
    if (effect) {
        // Get the dependency table for target
        let depsMap = targetMap.get(target)
        if(! depsMap) { depsMap =new Map()
            targetMap.set(target, depsMap)
        }
        // Get the set of response functions corresponding to key
        let deps = depsMap.get(key)
        if(! deps) { deps =new Set()
            depsMap.set(key, deps)
        }
        if(! deps.has(effect)) { deps.add(effect) } } }// Trigger the target.key response function
function trigger(target, key) {
    // Get the dependency table
    const depsMap = targetMap.get(target)
    if (depsMap) {
        // Get the set of response functions
        const deps = depsMap.get(key)
        if (deps) {
            // Execute all response functions
            deps.forEach(effect= > {
                effect()
            })
        }
    }
}


// Test the code
  const state = reactive({ name: 'Joe' })
  // The first time the value is printed, the state
  effect(() = > console.log(state.name)) 
  state.name = 'bill'
Copy the code