vue3 effect 01. track 以响应式代码为例子
packages/reactivity/src/baseHandlers.ts 中 createGetter 该方法为 Proxy 提供 Getter 的工厂方法
在判断,特殊返回完成之后,会进行 track
1 2 3 if (!isReadonly) { track (target, TrackOpTypes .GET , key) }
packages/reactivity/src/effect.ts 214
首先会判断 houldTrack && activeEffect, 这里默认它为true,因为要解释是否为true,需要知道创建和绑定的流程,以及它的规则。
1 if (shouldTrack && activeEffect) { ... }
接下来是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 ())) } trackEffects (dep, eventInfo)
targetMap
1 2 type KeyToDepMap = Map <any , Dep >const targetMap = new WeakMap <any , KeyToDepMap >()
定义 target => key => Dep 的一个映射表,通过他来存储之间的关系。 其中 KeyToDepMap 就是 depsMap, 最终存储 Dep
Dep 是一个
1 2 3 4 5 6 7 8 9 10 11 export type Dep = Set <ReactiveEffect > & TrackedMarkers type TrackedMarkers = { w : number n : number }
trackEffects
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export function trackEffects ( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) {let shouldTrack = false dep.add (activeEffect!) activeEffect!.deps .push (dep) }
这一部分就是增加 track 的代码,总之就是在 dep 和 effect 相互关联, dep 在 targetMap 中。
02. triggger triggger function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 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) { return } let deps : (Dep | undefined )[] = [] if (type === TriggerOpTypes .CLEAR ) { deps = [...depsMap.values ()] } else if (key === 'length' && isArray (target)) { const newLength = Number (newValue) depsMap.forEach ((dep, key ) => { if (key === 'length' || key >= newLength) { deps.push (dep) } }) } else { if (key !== void 0 ) { deps.push (depsMap.get (key)) } 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)) { 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) } } if (__DEV__) { triggerEffects (createDep (effects), eventInfo) } else { triggerEffects (createDep (effects)) } } }
看起来代码很多,其实做了几件事。
处理 type.clear
处理 arr & key = length or number 的问题
deps = Dep[], 如果 Key != undefined 把对应 key,以及根据type 获取对应Dep
triggerEffects 处理
triggerEffects
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export function triggerEffects ( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { const effects = isArray (dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed ) { triggerEffect (effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed ) { triggerEffect (effect, debuggerEventExtraInfo) } } }
通过遍历 dep, triggerEffect(effect), 先处理 computed, 然后非 computed
triggerEffect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 () } } }
触发 ReactiveEffect 有 scheduler 优先 scheduler,没有 run
track & trigger 总结
track,创建Dep,并且放入 targetMap & desMap, Dep 添加 activeEffect, activeEffect.deps.push(dep)
trigger, 获取 target 的 depsMap, 排除 type.clear & key = length & number, 如果有key 从 depsMap 获取, 然后再把对应type的一些固定key,也获取出来,然后 effect.scheduler or run, computed 优先。
03. reactiveEffect 源码 之前说了这么多,其实 track 和 trigger 都是为了触发 reactiveEffect 更新,那么继续看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 export class ReactiveEffect <T = any > { active = true deps : Dep [] = [] parent : ReactiveEffect | undefined = undefined computed?: ComputedRefImpl <T> allowRecurse?: boolean private deferStop?: boolean onStop?: () => void onTrack?: (event: DebuggerEvent ) => void onTrigger?: (event: DebuggerEvent ) => void constructor ( public fn: () => T, public scheduler: EffectScheduler | null = null , scope?: EffectScope ) { recordEffectScope (this , scope) } run ( ) { if (!this .active ) { return this .fn () } let parent : ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack while (parent) { if (parent === this ) { return } parent = parent.parent } try { this .parent = activeEffect activeEffect = this shouldTrack = true trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { initDepMarkers (this ) } else { cleanupEffect (this ) } return this .fn () } finally { if (effectTrackDepth <= maxMarkerBits) { finalizeDepMarkers (this ) } trackOpBit = 1 << --effectTrackDepth activeEffect = this .parent shouldTrack = lastShouldTrack this .parent = undefined if (this .deferStop ) { this .stop () } } } stop ( ) { if (activeEffect === this ) { this .deferStop = true } else if (this .active ) { cleanupEffect (this ) if (this .onStop ) { this .onStop () } this .active = false } } }
提供了3个方法,构造,run,stop
构造函数, 就是如果 !!scope, scope.effects.push(this), 还有一个 EffectScope,就是这个 scope,用于管理 reactiveEffect, 这里把他放进管理里面去
run 可以简单的理解为执行构造函数传入的 Fn 函数 2.1 复杂一点说,当 !active, 直接调用 fn 2.2 检查 parent 是否存在循环引用 2.3 设置参数,parent = activeEffect, activeEffect = this, shouldTrack = true 2.4 fn() 2.5 恢复参数 activeEffect = this.parent,shouldTrack = lastShouldTrack,this.parent = undefined
这里有一个概念
depMarkers 是一个用于追踪依赖关系的标记对象,用于在 reactive effect 中记录哪些依赖项已被追踪过。它有两个属性 w 和 n,分别代表了 “wasTracked” 和 “newTracked”。
在 reactive effect 中,depMarkers 被用来判断某个依赖项是否已被追踪,以及在追踪完所有依赖项后是否有新的依赖项被追踪。如果一个依赖项在 reactive effect 中被追踪过,它的 w 位会被设置为 1,如果在当前追踪过程中有新的依赖项被追踪,它的 n 位会被设置为 1。
通过 depMarkers 中的这些标记,Vue3 可以在 reactive effect 中跟踪依赖项的变化,并且只会在必要的时候重新运行 reactive effect。这样可以提高性能和响应速度。
这里先不展开了
stop 就是清理 effect 和 dep 的关联,全部delete,active 设置为 false
04. 怎么调用 reactiveEffect 这里我产生了一个疑惑,在这个触发的过程中,activeEffect, 是什么时候实例化的?我查看了所有关于 activeEffect 的引用。
只有在 reactiveEffect.run 中才有设置
1 2 3 4 5 6 7 8 9 this .parent = activeEffectactiveEffect = this shouldTrack = true activeEffect = this .parent shouldTrack = lastShouldTrack this .parent = undefined
也就是说当一个, 我们可以理解 reactiveEffect 是一个树,保证运行时候类似于先进后出。
这里解释了为什么 activeEffect 会保证有值的原因。
另外我们总是需要先调用 reactiveEffect, 才会有第一次的值, 这里是在初始化组件或者更新组件的时候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const effect = (instance.effect = new ReactiveEffect ( componentUpdateFn, () => queueJob (update), instance.scope )) const update : SchedulerJob = (instance.update = () => effect.run ())update.id = instance.uid toggleRecurse (instance, true )if (__DEV__) { effect.onTrack = instance.rtc ? e => invokeArrayFns (instance.rtc !, e) : void 0 effect.onTrigger = instance.rtg ? e => invokeArrayFns (instance.rtg !, e) : void 0 update.ownerInstance = instance } update ()
05. 总结。 reactiveEffect 是响应式的核心代码,所有的值都通过他来执行响应式,通过 run or stop 来控制执行或者停止。
通过 targetMap 来存储 reactiveEffect.
targetMap => depsMap => Dep => reactiveEffect.
通过 track 方法来创建 Dep,并且和当前的 reactivEffect 产生关联,
activeEffect.deps.push(dep)
dep.add(activeEffect)
通过 trigger 方法,来找到对应的 reactiveEffect, 不仅仅包含当前 key 的,还包含对用操作类型的 reactiveEffect
然后统一去触发。