Responsive principle
Vue2 uses Object.defineProperty Vue3 uses Proxy
Shortcomings of 2.0
The object can only hijack the set data, and the new data needs Vue.Set(xxx) array can only operate seven methods, modifying a certain value cannot be hijacked.
Implementation of reactive and effect
export const reactive = <T extends object>(target:T) => {
return new Proxy(target,{
get (target,key,receiver) {
const res = Reflect.get(target,key,receiver) as object
return res
},
set (target,key,value,receiver) {
const res = Reflect.set(target,key,value,receiver)
return res
}
})
}
The responsive principle of Vue3 relies on the core API of Proxy, through which certain operations of objects can be hijacked.
effect track trigger
Implement the effect side effect function
let activeEffect;
export const effect = (fn:Function) => {
const _effect = function () {
activeEffect = _effect;
fn()
}
_effect()
}
Use a global variable active to collect the current side effect function, and call it when initializing
implement track
const targetMap = new WeakMap()
export const track = (target,key) =>{
let depsMap = targetMap.get(target)
if(!depsMap){
depsMap = new Map()
targetMap.set(target,depsMap)
}
let deps = depsMap.get(key)
if(!deps){
deps = new Set()
depsMap.set(key,deps)
}
deps.add(activeEffect)
}
After the execution is completed, we get a data structure as follows
implement the trigger
export const trigger = (target,key) => {
const depsMap = targetMap.get(target)
const deps = depsMap.get(key)
deps.forEach(effect=>effect())
}
When we make an assignment, we will call set and then trigger the side effect function of collection
import {track,trigger} from './effect'
export const reactive = <T extends object>(target:T) => {
return new Proxy(target,{
get (target,key,receiver) {
const res = Reflect.get(target,key,receiver) as object
track(target,key)
return res
},
set (target,key,value,receiver) {
const res = Reflect.set(target,key,value,receiver)
trigger(target,key)
return res
}
})
}
Add these two methods to reactive
test code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
<script type="module">
import { reactive } from './reactive.js'
import { effect } from './effect.js'
const user = reactive({
name: "小满",
age: 18
})
effect(() => {
document.querySelector('#app').innerText = `${user.name} - ${user.age}`
})
setTimeout(()=>{
user.name = '大满很吊'
setTimeout(()=>{
user.age = '23'
},1000)
},2000)
</script>
</body>
</html>
Recursive implementation of reactive
import { track, trigger } from './effect'
const isObject = (target) => target != null && typeof target == 'object'
export const reactive = <T extends object>(target: T) => {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver) as object
track(target, key)
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
trigger(target, key)
return res
}
})
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
<script type="module">
import { reactive } from './reactive.js'
import { effect } from './effect.js'
const user = reactive({
name: "小满",
age: 18,
foo:{
bar:{
sss:123
}
}
})
effect(() => {
document.querySelector('#app').innerText = `${user.name} - ${user.age}-${user.foo.bar.sss}`
})
setTimeout(()=>{
user.name = '大满很吊'
setTimeout(()=>{
user.age = '23'
setTimeout(()=>{
user.foo.bar.sss = 66666666
},1000)
},1000)
},2000)
</script>
</body>
</html>