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