Vue3系列之effect和ReactiveEffect?track?trigger源碼解析
引言
介紹幾個API的時候,我們發(fā)現(xiàn)里面常出現(xiàn)effect、track和trigger,雖然簡單說了下track用于依賴收集,trigger來觸發(fā)更新。但是畢竟沒看到具體實現(xiàn),心里沒底。如今便可以一探究竟。
一、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的值為數(shù)據(jù)集Map。
const targetMap = new WeakMap<any, KeyToDepMap>() export let activeEffect: ReactiveEffect | undefined export let shouldTrack = true const trackStack: boolean[] = []
以及控制暫停、恢復依賴收集的函數(shù):
// 暫停收集
export function pauseTracking() {
trackStack.push(shouldTrack)
shouldTrack = false
}
// 恢復收集
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
// 重置為上一次的狀態(tài)
export function resetTracking() {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
2. class 聲明
在構造器中初始化fn ( 執(zhí)行run()的過程中調用 ) 、調度器scheduler,并通過recordEffectScope來記錄實例的作用域;聲明一些實例屬性,以及run、stop兩個方法:
active:boolean類型,表示當前的effect是否起作用;deps:當前effect的依賴;parent:指向上一個活躍的effect,形成鏈表;computed:可選,在computed函數(shù)得到的ComputedRefImpl里的effect具有這個屬性;allowRecurse,可選,表示是否允許自調用;deferStop:私有,可選,表示stop()是否延遲執(zhí)行;onStop:可選,函數(shù),在執(zhí)行stop()時會調用onStop;onTrackonTrigger:這兩個listener為調試用,分別在依賴收集和響應式更新時觸發(fā);- run:
effect最核心的方法。 stop:調用cleanupEffect讓effect停止起作用,如果是stop當前活躍的effect,也就是自己停止自己,則會將deferStop調為true,從而延遲停止的時機;觸發(fā)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 的代碼塊發(fā)生異常時執(zhí)行
if (effectTrackDepth <= maxMarkerBits) {
// 該方法遍歷 this.deps,將其中過氣的effect刪除,未捕獲的effect加入
// effect 就是其中的 dep
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
// 復原一些狀態(tài)
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
// 若設置了延遲停止,則執(zhí)行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 函數(shù)
1. 相關ts類型
effect函數(shù)有幾個相關的類型:
ReactiveEffectOptions:effect函數(shù)的入?yún)㈩愋椭唬?/li>ReactiveEffectRunner:是一個函數(shù),且具有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. 函數(shù)聲明
effect函數(shù)有兩個入?yún)ⅲ?/p>
fn:是一個函數(shù),經處理后用于創(chuàng)建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
}
// 根據(jù) fn 創(chuàng)建一個 _effect
const _effect = new ReactiveEffect(fn)
if (options) {
// 用 options 覆蓋 _effect 上的屬性
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
// 沒有 lazy , 則 _effect 立即執(zhí)行一次 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函數(shù)
stop用于清除effect。入?yún)?code>ReactiveEffectRunner;
export function stop(runner: ReactiveEffectRunner) {
runner.effect.stop()
}
三、track 依賴收集
1. track
一直在說track進行依賴收集,這里看下它到底怎么做的。
- 以目標對象
target為key,depsMap為targetMap的值;以target的key為key,使用createDep()創(chuàng)建依賴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創(chuàng)建一個新的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
觸發(fā)更新實際上就是觸發(fā)副作用,因此這一小節(jié)決定以與track相反的順序來介紹。
1. triggerEffect
triggerEffect觸發(fā)副作用從而更新。當觸發(fā)更新的副作用effect允許自調用,且不是當前活躍的副作用時,通過調度器scheduler執(zhí)行副作用或者直接執(zhí)行run,是實際上觸發(fā)更新的地方。
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
// 實際觸發(fā)更新的地方
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
2. triggerEffects
接收一個dep和用于調試的額外信息。遍歷dep中的effect,逐一使用triggerEffect來執(zhí)行副作用。源碼在這里有點蜜汁操作。
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
const effects = isArray(dep) ? dep : [...dep]
// 兩者互斥,但是執(zhí)行的操作相同?而且為什么不寫在一個 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觸發(fā)更新,其實是現(xiàn)在已經知道了,實際是triggerEffect來執(zhí)行副作用從而實現(xiàn)更新。
這里是創(chuàng)建一個deps數(shù)組,根據(jù)target、key和觸發(fā)更新的操作類型type等參數(shù),來獲取所有的相關dep,放入deps。再取出deps中所有的dep里的所有effect,放入effects列表中,通過triggerEffects(effects)來觸發(fā)所有的相關副作用,最終實現(xiàn)更新。
需要注意的是對于數(shù)組:
- 修改
length屬性會導致該數(shù)組所有依賴的更新; - 修數(shù)組新增成員會引起
length屬性相關的依賴的更新,因為length的值發(fā)生了變化。
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方法,將觸發(fā)全部相關的副作用
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
// 目標對象是數(shù)組,且修改了length屬性時,會觸發(fā)全部相關的副作用
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)) {
// 數(shù)組下標成員的更改 會引起 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接受的參數(shù)類型為Set,之前的是數(shù)組
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}
五、小結
1. 依賴收集
targetMap中有depsMap(以target為key);depsMap中有許多dep(以targetMap的key為key);簡單理解為:在編譯時根據(jù)target和key,創(chuàng)建副作用,將activeEffect指向新建的副作用,并存放到相關的依賴dep里的過程就是依賴收集。
2. 觸發(fā)更新
反過來,觸發(fā)target、key相關的dep中所有相關的副作用,通過各個副作用上的effect.scheduler()或者effect.run()來實現(xiàn)更新。
以上就是Vue3系列之effect和ReactiveEffect track trigger源碼解析的詳細內容,更多關于Vue3 effect和ReactiveEffect track trigger的資料請關注腳本之家其它相關文章!
相關文章
在Vue中導入并讀取Excel數(shù)據(jù)的操作步驟
在工作中遇到需要前端上傳excel文件獲取到相應數(shù)據(jù)處理之后傳給后端并且展示上傳文件的數(shù)據(jù),所以本文就來給大家介紹一下Vue中導入并讀取Excel數(shù)據(jù)的操作步驟,需要的朋友可以參考下2023-08-08

