目录
- 引言
- 一、ReactiveEffect
- 1. 相关的全局变量
- 2. class 声明
- 3. cleanupEffect
- 二、effect 函数
- 1. 相关ts类型
- 2. 函数声明
- 3. stop函数
- 三、track 依赖收集
- 1. track
- 2. createDep
- 3. trackEffects
- 4. 小结
- 四、trigger
- 1. triggerEffect
- 2. triggerEffects
- 3. trigger
- 五、小结
- 1. 依赖收集
- 2. 触发更新
引言
介绍几个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。
const targetMap = new WeakMap<any, KeyToDepMap>() | |
export let activeEffect: ReactiveEffect | undefined | |
export let shouldTrack = true | |
const trackStack: boolean[] = [] |
以及控制暂停、恢复依赖收集的函数:
// 暂停收集 | |
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。
export class ReactiveEffect<T = any> { | |
active = true | |
deps: Dep[] = [] | |
parent: ReactiveEffect | undefined = undefined | |
/** | |
* Can be attached after creation | |
* @internal | |
*/ | |
computed?: ComputedRefImpl<T> | |
/** | |
* @internal | |
*/ | |
allowRecurse?: boolean | |
/** | |
* @internal | |
*/ | |
private deferStop?: boolean | |
onStop?: () => void | |
// dev only | |
onTrack?: (event: DebuggerEvent) => void | |
// dev only | |
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() | |
} | |
// 当前活跃的effect | |
let parent: ReactiveEffect | undefined = activeEffect | |
let lastShouldTrack = shouldTrack | |
// 如果当前活跃的effect就是这个effect本身,则直接返回 | |
while (parent) { | |
if (parent === this) { | |
return | |
} | |
parent = parent.parent | |
} | |
// 依次活跃的effect形成链表,由parent属性连接 | |
try { | |
this.parent = activeEffect | |
activeEffect = this | |
shouldTrack = true | |
trackOpBit = 1 << ++effectTrackDepth | |
if (effectTrackDepth <= maxMarkerBits) { | |
// 遍历 this.deps 将其中的effect设置为已捕获 tracked | |
initDepMarkers(this) | |
} else { | |
// 层级溢出则清除当前副作用 | |
cleanupEffect(this) | |
} | |
// 尾调用传入的fn | |
return this.fn() | |
} finally { | |
// 因为前面有return,因此当 try 的代码块发生异常时执行 | |
if (effectTrackDepth <= maxMarkerBits) { | |
// 该方法遍历 this.deps,将其中过气的effect删除,未捕获的effect加入 | |
// effect 就是其中的 dep | |
finalizeDepMarkers(this) | |
} | |
trackOpBit = 1 << --effectTrackDepth | |
// 复原一些状态 | |
activeEffect = this.parent | |
shouldTrack = lastShouldTrack | |
this.parent = undefined | |
// 若设置了延迟停止,则执行stop,进行延迟清理 | |
if (this.deferStop) { | |
this.stop() | |
} | |
} | |
} | |
// 清除副作用 | |
stop() { | |
// stopped while running itself - defer the cleanup | |
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。
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属性的类型;
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上的属性。
export function effect<T = any>( | |
fn: () => T, | |
options?: ReactiveEffectOptions | |
): ReactiveEffectRunner { | |
// 处理fn | |
if ((fn as ReactiveEffectRunner).effect) { | |
fn = (fn as ReactiveEffectRunner).effect.fn | |
} | |
// 根据 fn 创建一个 _effect | |
const _effect = new ReactiveEffect(fn) | |
if (options) { | |
// 用 options 覆盖 _effect 上的属性 | |
extend(_effect, options) | |
if (options.scope) recordEffectScope(_effect, options.scope) | |
} | |
// 没有 lazy , 则 _effect 立即执行一次 run() | |
if (!options || !options.lazy) { | |
_effect.run() | |
} | |
// runner:拿到 _effect.run 并挂上 effect 属性,包装成 ReactiveEffectRunner 类型 | |
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner | |
// effect属性指回 _effect 自身,方便使用 runner 调用 run 和 stop | |
runner.effect = _effect | |
// 返回 runner | |
return runner | |
} |
3. stop函数
stop用于清除effect。入参为ReactiveEffectRunner;
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)来收集副作用。
// 全局变量 targetMap | |
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的首字母,表示当前依赖是否是新收集的。
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中加入该副作用影响到的所有依赖。
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!) | |
} | |
// 当前依赖 dep 还未被捕获 / 当前依赖 dep 中,还没有当前活跃的副作用时, | |
// 将当前活跃的副作用 effect 添加进 dep 里,同时在把 dep 加入受副作用影响的依赖集合 activeEffect.deps 中 | |
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,是实际上触发更新的地方。
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来执行副作用。源码在这里有点蜜汁操作。
export function triggerEffects( | |
dep: Dep | ReactiveEffect[], | |
debuggerEventExtraInfo?: DebuggerEventExtraInfo | |
) { | |
// spread into array for stabilization | |
const effects = isArray(dep) ? dep : [...dep] | |
// 两者互斥,但是执行的操作相同?而且为什么不写在一个 for...of... 里 ? | |
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的值发生了变化。
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) { | |
// 调用了Set、Map实例的clear方法,将触发全部相关的副作用 | |
// collection being cleared | |
// trigger all effects for target | |
deps = [...depsMap.values()] | |
} else if (key === 'length' && isArray(target)) { | |
// 目标对象是数组,且修改了length属性时,会触发全部相关的副作用 | |
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)) { | |
// 数组下标成员的更改 会引起 length 属性相关的更新 | |
// 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接受的参数类型为Set,之前的是数组 | |
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()来实现更新。