持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
前言
上篇我们了解了 effect 的用法和作用,并了解到了依赖收集到派发更新的具体流程,这节我们就详细看看如何进行依赖收集和派发更新。
依赖收集 track
我在前面的 reactive 方法中讲到了,我们会将对象通过 Proxy 转换成代理对象,并劫持 get 方法、set方法。而在 get 方法中就行进行依赖收集。
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
/** 省略 */
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
/** 省略 */
}
}
track 就是依赖收集的方法,传入三个参数,分别是目标对象target、get、具体访问的那个key。
track 方法的源码位于 packages/reactivity/src/effect.ts
// packages/reactivity/src/effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
}
- 首先判断 activeEffect 是否存在,也就是当前是否有 effect 被执行,没有的话就不进行收集
- 然后再判断 targetMap 中是否已经存在了 target 的depsMap,不存在就初始化一个depsMap
- 判断 depsMap 是否已经有这个key的依赖对象,没有就创建一个 new Set() 对象
- 最后调用 trackEffects 传入 dep
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit // set newly tracked
shouldTrack = !wasTracked(dep)
}
} else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect!)
}
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
}
}
在 trackEffects 中将当前执行的 activeEffect 保存到 dep依赖中,所以每个依赖也是一个 effect 函数,到时候派发更新时调用 effect.run 就能更新了。
派发更新 trigger
派发更新是在 set 陷阱中,当设置目标值是会触发。
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
这里会调用 trigger 方法,如果是新增,就传入目标对象、add、key和value。
如果有旧值的存在则,传入目标对象、set、key、新的value 和 旧的value
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
triggerEffects(createDep(effects))
}
}
它根据 target 从 targetMap 中获取到了 depsMap,然后再把 depsMap 中的所有依赖 effect 函数,放入数组 deps 中。 然后triggerEffects 进行遍历,最终分别调用 triggerEffect 方法
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
在 triggerEffect 中,调用了每个依赖的 run 方法,我们从前面的例子中得知,在run中会执行我们传进行的更新方法,最后成功更新所有依赖。
小结
这篇更详细的了解了如何进行依赖收集和更新,并知道了,其实每个依赖都是一个effect函数。