前面几篇文章里,介绍几个API的时候,我们发现里面常出现effect、track和trigger,虽然简单说了下track用于依赖收集,trigger来触发更新。但是毕竟没看到具体实现,心里没底。如今便可以一探究竟。
一、ReactiveEffect
1. 相关的全局变量
之前提到的effect,便是ReactiveEffect的实例。用到了一些重要的全局变量。
targetMap:弱映射,以目标对象target为key,其收集到的依赖集depsMap为值,因此通过目标对象target可以获取到对应的所有依赖;
activeEffect:当前活跃的effect,随后会被收集起来;
shouldTrack:用作暂停和恢复依赖收集的标志;
trackStack:历史shouldTrack的记录栈。
targetMap对比reactive篇章中提到的proxyMap:
- 两者都是弱映射;
- 都以目标对象
target为key;
targetMap全局只有一个;而proxyMap有四种,分别对应reactive、shallowReactive、readonly、shallowReadonly;
- 一个
target在一种proxyMap中最多只有一个对应的代理proxy,因此proxyMap的值为单个的proxy对象;
- 一个
target可以由很多的依赖dep,因此targetMap的值为数据集Map。
1 2 3 4 5 6
| const targetMap = new WeakMap<any, KeyToDepMap>();
export let activeEffect: ReactiveEffect | undefined;
export let shouldTrack = true; const trackStack: boolean[] = [];
|
以及控制暂停、恢复依赖收集的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export function pauseTracking() { trackStack.push(shouldTrack); shouldTrack = false; }
export function enableTracking() { trackStack.push(shouldTrack); shouldTrack = true; }
export function resetTracking() { const last = trackStack.pop(); shouldTrack = last === undefined ? true : last; }
|
2. class 声明
在构造器中初始化fn ( 执行run()的过程中调用 ) 、调度器scheduler,并通过recordEffectScope来记录实例的作用域;声明一些实例属性,以及run、stop两个方法:
active:boolean类型,表示当前的effect是否起作用;
deps:当前effect的依赖;
parent:指向上一个活跃的effect,形成链表;
computed:可选,在computed函数得到的ComputedRefImpl里的effect具有这个属性;
allowRecurse,可选,表示是否允许自调用;
deferStop:私有,可选,表示stop()是否延迟执行;
onStop:可选,函数,在执行stop()时会调用onStop;
onTrack
onTrigger:这两个listener为调试用,分别在依赖收集和响应式更新时触发;
- run:
effect最核心的方法。
stop:调用cleanupEffect让effect停止起作用,如果是stop当前活跃的effect,也就是自己停止自己,则会将deferStop调为true,从而延迟停止的时机;触发onStop;将active调为false。
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
| 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. cleanupEffect
cleanupEffect用于清除副作用。接收一个effect,遍历effect.deps,并逐个删除副作用effect。随后清空effect.deps。
1 2 3 4 5 6 7 8 9
| function cleanupEffect(effect: ReactiveEffect) { const { deps } = effect; if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect); } deps.length = 0; } }
|
二、effect 函数
1. 相关ts类型
effect函数有几个相关的类型:
ReactiveEffectOptions:effect函数的入参类型之一;
ReactiveEffectRunner:是一个函数,且具有effect属性的类型;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export interface DebuggerOptions { onTrack?: (event: DebuggerEvent) => void; onTrigger?: (event: DebuggerEvent) => void; }
export interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean; scheduler?: EffectScheduler; scope?: EffectScope; allowRecurse?: boolean; onStop?: () => void; }
export interface ReactiveEffectRunner<T = any> { (): T; effect: ReactiveEffect; }
|
2. 函数声明
effect函数有两个入参:
fn:是一个函数,经处理后用于创建 ReactiveEffect实例_effect;
options:可选,用于覆盖_effect上的属性。
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
| export function effect<T = any>( fn: () => T, options?: ReactiveEffectOptions ): ReactiveEffectRunner { if ((fn as ReactiveEffectRunner).effect) { fn = (fn as ReactiveEffectRunner).effect.fn; }
const _effect = new ReactiveEffect(fn); if (options) { extend(_effect, options); if (options.scope) recordEffectScope(_effect, options.scope); } if (!options || !options.lazy) { _effect.run(); }
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner; runner.effect = _effect;
return runner; }
|
3. stop函数
stop用于清除effect。入参为ReactiveEffectRunner;
1 2 3
| export function stop(runner: ReactiveEffectRunner) { runner.effect.stop(); }
|
三、track 依赖收集
1. track
一直在说track进行依赖收集,这里看下它到底怎么做的。
- 以目标对象
target为key,depsMap为targetMap的值;以target的key为key,使用createDep()创建依赖dep为值,存放在target对应的depsMap中。
- 通过
trackEffects(dep, eventInfo)来收集副作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const targetMap = new WeakMap<any, KeyToDepMap>();
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); } }
|
2. createDep
使用createDep创建一个新的dep。可以看到,dep是个Set实例,且添加了两个属性:
w:wasTracked的首字母,表示当前依赖是否被收集;
n:newlyTracked的首字母,表示当前依赖是否是新收集的。
1 2 3 4 5 6
| export const createDep = (effects?: ReactiveEffect[]): Dep => { const dep = new Set<ReactiveEffect>(effects) as Dep; dep.w = 0; dep.n = 0; return dep; };
|
3. trackEffects
trackEffects用于收集副作用。主要把当前活跃的activeEffect加入dep,以及在activeEffect.deps中加入该副作用影响到的所有依赖。
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
| export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false; if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { dep.n |= trackOpBit; shouldTrack = !wasTracked(dep); } } else { shouldTrack = !dep.has(activeEffect!); }
if (shouldTrack) { dep.add(activeEffect!); activeEffect!.deps.push(dep); if (__DEV__ && activeEffect!.onTrack) { activeEffect!.onTrack({ effect: activeEffect!, ...debuggerEventExtraInfo!, }); } } }
|
4. 小结
用一句比较拗口的话来说,依赖收集就是把当前活跃的副作用activeEffect存入全局变量targetMap中的 ( target 对应的 depsMap) 中 (target的key)对应的 dep ( 类型为Set) 中,并把这个dep加入到受activeEffect副作用影响的所有依赖activeEffect.deps列表中。
四、trigger
触发更新实际上就是触发副作用,因此这一小节决定以与track相反的顺序来介绍。
1. triggerEffect
triggerEffect触发副作用从而更新。当触发更新的副作用effect允许自调用,且不是当前活跃的副作用时,通过调度器scheduler执行副作用或者直接执行run,是实际上触发更新的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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(); } } }
|
2. triggerEffects
接收一个dep和用于调试的额外信息。遍历dep中的effect,逐一使用triggerEffect来执行副作用。源码在这里有点蜜汁操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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); } } }
|
3. trigger
之前一直说trigger触发更新,其实是现在已经知道了,实际是triggerEffect来执行副作用从而实现更新。
这里是创建一个deps数组,根据target、key和触发更新的操作类型type等参数,来获取所有的相关dep,放入deps。再取出deps中所有的dep里的所有effect,放入effects列表中,通过triggerEffects(effects)来触发所有的相关副作用,最终实现更新。
需要注意的是对于数组:
- 修改
length属性会导致该数组所有依赖的更新;
- 修数组新增成员会引起
length属性相关的依赖的更新,因为length的值发生了变化。
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
| 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)) { depsMap.forEach((dep, key) => { if (key === "length" || key >= (newValue as number)) { 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)); } } }
|
五、小结
1. 依赖收集
targetMap中有depsMap(以target为key);depsMap中有许多dep(以targetMap的key为key);简单理解为:在编译时根据target和key,创建副作用,将activeEffect指向新建的副作用,并存放到相关的依赖dep里的过程就是依赖收集。
2. 触发更新
反过来,触发target、key相关的dep中所有相关的副作用,通过各个副作用上的effect.scheduler()或者effect.run()来实现更新。