reactivity响应式
之前简单实现了dep的监听和更新,具体的在vue2和vue3中分别是使用了 Object.defineProperty
Proxy
作为监听,而不是只监听.value属性
首先是调用reactive方法传入一个对象,开始监听这个对象,当这个对象发生改变时,调用watchEffect传入的方法,这就是要达到的结果
const state = reactive({
count: 0
})
watchEffect(() => {
console.log(state.msg);
})
再接着围绕这个结果开始构建出 reactive 函数和 watchEffect
let activeEffect
function watchEffect (effect) {
activeEffect = effect
effect()
activeEffect = null
}
watchEffect还是和上一篇一样,定义一个变量保存,以便收集依赖时添加进set
再把之前的收集依赖和派发更新一同拷贝过来
class Dep {
subscribers = new Set()
depend () {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify (effect) {
this.subscribers.forEach(effect => {
effect()
})
}
}
然后就是reactive的实现方法,在vue2中,因为兼容性,采用了Object.defineProperty,分别监听每一个对象的键值
function reactive (raw) {
Object.keys(raw).forEach((key) => {
const dep = new Dep()
let value = raw[key]
Object.defineProperty(raw, key, {
get () {
dep.depend()
return value
},
set (newValue) {
value = newValue
dep.notify()
}
})
})
return raw
}
vue3中,则直接使用es6的proxy监听整个对象,更加提升效率,不用每个都去遍历,也可以监听到新增和删除的属性,还有像array这种对象不用再hook里面的七个常用方法就可以直接监听到
function reactive (raw) {
return new Proxy(raw, reactiveHandles)
}
接着就去详细实现reactiveHandles里面的方法
const targetMap = new WeakMap()
function getDep (target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Dep()
depsMap.set(key, dep)
}
return dep
}
const reactiveHandles = {
get (target, key, receiver) {
const dep = getDep(target, key)
dep.depend()
// return target[key]
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
const dep = getDep(target, key)
const result = Reflect.set(target, key, value, receiver)
dep.notify()
return result
}
}
首先通过get、和set方法监听取值和赋值操作,在get里添加依赖,添加收集的时候用key作为键dep对象作为值,这里添加了一个WeakMap弱引用到这个对象,当这个对象没有依赖时,便于垃圾回收清理。
在set中拿到对应key的dep,触发watchEffect里面的方法,达到响应式
set和get中调用的Reflect,可以去看这篇文章,详细的介绍了Reflect.get(target, key, receiver) 和 target[key] 的区别
这里每一个key都对应一个新的dep对象,dep里的subscribers可能会有收集到的effect函数,因为如果watchEffect传入的函数没有访问这个key,也就不会触发get收集函数
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script>
let activeEffect
class Dep {
subscribers = new Set()
depend () {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify (effect) {
this.subscribers.forEach(effect => {
effect()
})
}
}
function watchEffect (effect) {
activeEffect = effect
effect()
activeEffect = null
}
const targetMap = new WeakMap()
function getDep (target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Dep()
depsMap.set(key, dep)
}
return dep
}
const reactiveHandles = {
get (target, key, receiver) {
const dep = getDep(target, key)
dep.depend()
// return target[key]
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
const dep = getDep(target, key)
const result = Reflect.set(target, key, value, receiver)
dep.notify()
return result
}
}
// vue2 写法
/* function reactive (raw) {
Object.keys(raw).forEach((key) => {
const dep = new Dep()
let value = raw[key]
Object.defineProperty(raw, key, {
get () {
dep.depend()
return value
},
set (newValue) {
value = newValue
dep.notify()
}
})
})
return raw
} */
function reactive (raw) {
return new Proxy(raw, reactiveHandles)
}
const state = reactive({
count: 0
})
watchEffect(() => {
console.log(state.msg);
})
state.count++
</script>