Vue2 is updated to vue3 in response to data, with significant adjustments.
So let’s review the response formula for VUe2 what did we do
Object defineProperty()
Vue2 array responsive principle: override can modify the array of 7 methods, from the array prototype to obtain these 7 methods, and override can send update notification function implementation
Object property hijacking
// Specify the specified key interceptor
function defineReactive(obj, key, val) {
// recursive traversal
observe(val)
// Val is actually a closure
Object.defineProperty(obj, key, {
get() {
return val
},
set(newVal) {
if(newVal ! == val) {// Val can be an object
observe(newVal)
notifyUpdate()
val = newVal
}
}
})
}
Copy the code
Hijacking of array properties
// Modify the array's 7 API prototypes
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto) ; ['push'.'pop'.'shift'.'unshift'.'splice'.'reverse'.'sort'].forEach(
method= > {
arrayProto[method] = function() {
// Do what you did before
originalProto[method].apply(this.arguments)
// Notification update
notifyUpdate()
}
}
)
Copy the code
Data response
// Idea: recursively traverse the incoming OBj, defining interceptions for each attribute
function observe(obj) {
if (typeofobj ! = ='object' || obj == null) {
return obj
}
// Determine the type: if it is an array, replace its prototype
if (Array.isArray(obj)) {
Object.setPrototypeOf(obj, arrayProto)
} else {
const keys = Object.keys(obj)
for (let index = 0; index < keys.length; index++) {
const key = keys[index]
// Intercepts obj for each key
defineReactive(obj, key, obj[key])
}
}
}
Copy the code
With a New Deal with
function notifyUpdate() {
console.log('Page updated! ')}const data = { foo: 'foo'.bar: { a: 1 }, tua: [1.2.3] }
observe(data)
// 1. Common updates
// data.foo = 'foooooooo'
// 2. Nested attribute updates
// data.bar.a = 10
// data.dong = 'lalala' // no ok
// 3. Assignment is an object
// data.bar = {a:10}
4 / / array
// data.tua.push(4)
Copy the code
What’s wrong with this approach?
- Large amount of data needs to be regenerated, resulting in poor performance and high consumption of recursive traversal
- New or deleted properties cannot be listened on
- Array responsivity requires additional implementation
- There are limitations to changing the syntax
Therefore, we made a more optimized plan in VUE3
The realization of vue3’s responsivity principle
Vue3 response principle: UseProxyObject intercepts data
// WeakMap caches proxy data and raw data in a weak-reference way
const toProxy = new WeakMap(a)// obj: observed
const toRaw = new WeakMap(a)// Shape like observed: obj
function isObject(obj) {
return typeof obj === 'object' || obj === null
}
function hasOwn(obj, key) {
return obj.hasOwnProperty(key)
}
// Response object data
function reactive(obj) {
if(! isObject(obj)) {return obj
}
// Find the cache
if (toProxy.has(obj)) {
return toProxy.get(obj)
}
// Obj is a proxy object
if (toRaw.has(obj)) {
return obj
}
const observed = new Proxy(obj, {
get(target, key, receiver) {
/ / access
const res = Reflect.get(target, key, receiver)
console.log(` access${key}: ${res}`)
// Rely on collection
track(target, key)
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
// Add and update
const hadKey = hasOwn(target, key) / / ADD or SET
const oldVal = target[key]
const res = Reflect.set(target, key, value, receiver)
if(! hadKey) {console.log(` new${key}:${value}`)
trigger(target, 'ADD', key)
} else if(oldVal ! == value) {console.log(` set${key}:${value}`)
trigger(target, 'SET', key)
}
return res
},
deleteProperty(target, key) {
/ / delete
const hadKey = hasOwn(target, key)
const res = Reflect.deleteProperty(target, key)
// The key exists and was deleted successfully
if (res && hadKey) {
console.log(` delete${key}:${res}`)
trigger(target, 'DELETE', key)
}
return res
}
})
/ / cache
toProxy.set(obj, observed)
toRaw.set(observed, obj)
return observed
}
// The response callback method for each attribute
const activeReativeEffectStack = []
// Rely on collection execution
{target:{key:[eff1, eff2]}}
let targetsMap = new WeakMap(a)function track(target, key) {
// Get the response function from the stack
const effect = activeReativeEffectStack[activeReativeEffectStack.length - 1]
if (effect) {
let depsMap = targetsMap.get(target)
if(! depsMap) {// Access target for the first time
depsMap = new Map()
targetsMap.set(target, depsMap)
}
/ / store the key
let deps = depsMap.get(key)
if(! deps) { deps =new Set()
depsMap.set(key, deps)
}
if(! deps.has(effect)) { deps.add(effect) } } }// Data changes respond to callbacks
function effect(fn) {
// 1. Exception handling
// 2. Execute function
// 3. Place it on the Active EffectStack
const rxEffect = function(. args) {
try {
activeReativeEffectStack.push(rxEffect)
returnfn(... args)// Execute the function to trigger dependency collection
} finally {
activeReativeEffectStack.pop()
}
}
rxEffect() // Execute immediately by default
return rxEffect
}
// Trigger the target.key response function
function trigger(target, type, key) {
// Get the dependency table
const depsMap = targetsMap.get(target)
if (depsMap) {
// Get the set of response functions
const deps = depsMap.get(key)
const effects = new Set(a)if (deps) {
// Execute all response functions
deps.forEach(effect= > {
// effect()
effects.add(effect)
})
}
// Array added or deleted
if (type === 'ADD' || type === 'DELETE') {
if (Array.isArray(target)) {
const deps = depsMap.get('length')
if (deps) {
deps.forEach(effect= > {
effects.add(effect)
})
}
}
}
// Get the existing Dep Set execution
effects.forEach(effect= > effect())
}
}
const data = { foo: 'foo'.bar: { a: 1}}const react = reactive(data)
/ / 1. Access
// react.foo // ok
// 2. Set existing properties
// react.foo = 'foooooooo'
// 3. Set nonexistent properties
// react.baz = 'bazzzzzz'
// 4. Nested objects
// react.bar.a = 10
// Avoid duplicate proxies
// console.log(reactive(data) === react) // true
// reactive(react)
effect(() = > {
console.log('count has changed: ', react.foo)
// dom
})
react.foo = 'fooooooo'
Copy the code