依赖收集:通过自然地使用变量,来完成依赖的收集,当变量改变时,根据收集的依赖判断是否需要触发回调。
举个栗子:
const state = reactive({ count: 1 })
effect(() => {
// update ...
document.title = `${state.count}`
})
上面例子中,effect
收集内部依赖(内部使用到的变量) state.count
,当依赖改变时根据收集好的依赖关系判断是否有对应回调(更新)
依赖收集的实现
我们需要在每次 getter
的时候将对应的依赖关系确定,此后执行 setter
时就能知道那个依赖更新了,并执行对应的回调;所以我们可以使用 proxy
重写 setter
& getter
,将收集和更新代码置入其中。
const targetsMap = new WeakMap()
// { target { key [...effect]} }
function track(target, key) {
let depsMap = targetsMap.get(target)
if (!depsMap) {
targetsMap.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
deps.add(effect) // 添加动作
}
function trigger(target, key) {
const depsMap = targetsMap.get(target)
if (!depsMap) return
let deps = depsMap.get(key)
if (deps) {
deps.forEach((dp) => {
dp()
})
}
}
function reactive(target) {
const handlers = {
get(target, key, receiver) {
track(target, key) // 收集依赖
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const oldValue = target[key]
if (Reflect.set(target, key, value, receiver) && oldValue != value) {
trigger(target, key) // 依赖更新
}
return value
}
}
return new Proxy(target, handlers)
}
////////////////////////////////
const state = reactive({ count: 1 })
// 写死
function effect() {
// update ...
document.title = `${state.count}`
}
//手动触发 getter
effect()
// 当更新 state 的值时就会触发回调(effect)
state.count = 2
当 state 改变时 effect 将会更新 title,但此时 effect 是写死的,我想添加其他更新总不能一个个去加吧,此时重头戏才刚刚开始!
上面说了,在 getter 时,去依赖收集,如何自动触发更新内的 getter ?
实现之前的 effect
currentEffect = null
function effect(effect) {
// 保存effect,以便 getter时建立依赖
// 此时 track 只需将 currentEffect 加入deps即可
currentEffect = effect
// 触发 effect 内 getter 进行依赖收集
effect()
currentEffect = null // 收集完成
}
是的,只需要给他包装一层就行了,就像开始的例子一样,此时业务代码 effect 写法
effect(() => {
// update ...
document.title = `${state.count}`
})
// more ...
effect(() => {
// update ...
document.body.innerHTML = `${state.count}`
})
此时的代码:点我
ref
因为基础类型无法进行监听,所以它使用 xx.value
,扩展一下就行。
function ref(v) {
const def = {
get value() {
track(def, 'value')
return v
},
set value(newVal) {
v = newVal
trigger(def, 'value')
}
}
return def
}
computed
同样,effect 是执行一个函数,computed 返回数据, 只需要包装一下 effect ,保持返回结构就可以了!
function computed(computed) {
const result = ref() // 保存返回值
effect(() => {
result.value = computed
})
return result
}