引言
一、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
来触发更新。但是毕竟没看到具体实现,心里没底。如今便可以一探究竟。
之前提到的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
列表中。
触发更新实际上就是触发副作用,因此这一小节决定以与track
相反的顺序来介绍。
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
里的过程就是依赖收集。
反过来,触发target
、key
相关的dep
中所有相关的副作用,通过各个副作用上的effect.scheduler()
或者effect.run()
来实现更新。
以上就是Vue3系列之effect和ReactiveEffect track trigger源码解析的详细内容,更多关于Vue3 effect和ReactiveEffect track trigger的资料请关注软件开发网其它相关文章!