分析如下示例更新过程:
let obj = { name: "Long", age: 10, flag: true }
const state = reactive(obj);
effect(() => {
app.innerHTML = state.flag ? state.name : state.age
})
setTimeout(() => {
state.flag = false
}, 1000)
-
执行 effect 进行了依赖收集,收集的形式如下:
tergetMap target depsMap = new Map() flag: dep = new Map() name: dep = new Map()
-
由于首次执行 effect 传入的回调函数,访问
state.flag
和state.name
会进行依赖收集。会在 effect._trackId 为 1 的 effect 的 deps 数组上保存 flag 和 name 这两个 dep,同时也分别在 flag 的 dep 和 name 的 dep 上保存 effect._trackId 为 1 的 effect 。双向记忆。tergetMap target depsMap = new Map() flag: dep = new Map() effect(_trackId:1) name: dep = new Map() effect(_trackId:1)
effect deps: [ flag: dep = new Map(), name: dep = new Map() ]
-
当
state.flag = false
时触发 trigger,执行 flag 的 dep 中的 effect.run() -
执行 run 方法,会将
activeEffect = this
并调用preCleanEffect
将 effect._depsLength = 0 置为 0, effect._trackId++。此时的 activeEffect 已经变样了(即使是从之前的 flag 的 dep 中取出的 effect) -
执行 run 方法中的
this.fn()
就是执行 effect 的回调函数。访问 state.flag 和 state.age 引发依赖收集。
function preCleanEffect(effect) {
effect._depsLength = 0
effect._trackId++
}
run() {
this._dirtyLevel = DirtyLevels.NoDirty // 每次运行后effect变为no_dirty
// 让fn执行
if (!this.active) {
return this.fn() // 不是激活的,执行后,什么都不用做
}
let lastEffect = activeEffect
try {
activeEffect = this
// effect重新执行前,需要将上一次的依赖情况 effect.deps
preCleanEffect(this)
this._running++
return this.fn() // 依赖收集 -> state.name state.age
} finally {
this._running--
postCleanEffect(this)
activeEffect = lastEffect
}
}
stop() {
if (this.active) {
this.active = false // 后续来实现
preCleanEffect(this)
postCleanEffect(this)
}
}
- 访问 state.flag 时,触发 getter,调用 track,进而调用
trackEffect(activeEffect, dep)
。这个 activeEffect 可以理解为新的effect(length=0,trackId = 2)
,dep 是从flag 的 depsMap 中获取的 - 调用 trackEffect 函数执行
dep.get(effect) !== effect._trackId
时,dep.get(effect) 获取到的是 depsMap 中的 value 为 1。effect._trackId 已经在上次执行 run 方法的 fn 时调用 preCleanEffect 设置为 2 了。所以dep.get(effect) !== effect._trackId
为 true,调用 dep.set(effect, effect._trackId) 更新 id。此时 flag dep 上的 effect 的 trackId 也变成 2 了。当获取let oldDep = effect.deps[effect._depsLength]
时,effect.deps[0] 即 flag 的 dep,和 effect(之前也是从 flag 上获取的)进行比对。当然相等!就开始下一个effect._depsLength++
- 访问 state.age 同样触发 getter,调用 track,进而调用
trackEffect(activeEffect, dep)
。由于之前根本没有访问过 state.age 导致 age 的 dep 上没有 effect,即 dep.get(effect) 为 undefined, 而effect._trackId
已经为 2,不相等。则设置dep.set(effect, effect._trackId)
, age 的 dep value 为 2。当获取 oldDep 拿到的还是 name 的 dep ,明显和现在的 age 的 dep 不等,就清除掉 name 的 dep使用 age 的 dep 更新effect.deps[effect._depsLength++] = dep
export function trackEffect(effect, dep) {
// 收集时一个个收集的
// 需要重新的去收集依赖 , 将不需要的移除掉
// console.log(effect, dep);
if (dep.get(effect) !== effect._trackId) {
dep.set(effect, effect._trackId) // 更新id
// {flag,name}
// {flag,age
let oldDep = effect.deps[effect._depsLength]
// 如果没有存过
if (oldDep !== dep) {
if (oldDep) {
// 删除掉老的
cleanDepEffect(oldDep, effect)
}
// 换成新的
effect.deps[effect._depsLength++] = dep // 永远按照本次最新的来存放
} else {
effect._depsLength++
}
}
}