源碼分析Vue3響應式核心之effect
vue3響應式核心文章匯總:
vue3響應式核心之reactive源碼詳解
vue3響應式核心之effect源碼詳解
上篇vue3響應式核心之reactive源碼詳解詳細講解瞭reactive的原理和實現源碼,本篇文章將講解effect源碼。
通常情況下我們是不會直接使用effect的,因為effect是一個底層的API,在我們使用Vue3的時候Vue默認會幫我們調用effect。 effect翻譯為作用,意思是使其發生作用,這個使其的其就是我們傳入的函數,所以effect的作用就是讓我們傳入的函數發生作用,也就是執行這個函數。 執行過程簡圖如下:
接下來先通過例子瞭解effect的基本用法,然後再去瞭解原理。
一、effect用法
1、基本用法
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }) obj.count++
結果會先打印1, 然後在obj.count++
之後打印出2。
流程簡圖如下:
運行effect(fun)
// 先執行 fun() // 打印出1 const runner = new ReactiveEffect(fn) return runner runner: { run() { this.fun() //執行fun }, stop() { } }
console.log(obj.count)
track依賴收集 結構如下:
obj.count++
觸發依賴,執行runner.run(), 實際運行的是
() => { console.log(obj.count) }
所以又打印出2
2、lazy屬性為true
此值為 true 時,隻有在第一次手動調用 runner 後,依賴數據變更時,才會自動執行 effect 的回調,可以理解為 effect 的是在手動調用 runner 後才首次執行
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }, { lazy: true }) runner() obj.count++
隻會打印出2
原因是effect源碼中有如下邏輯:
3、options中包含onTrack
let events = [] const onTrack = (e) => { events.push(e) } const obj = reactive({ foo: 1, bar: 2 }) const runner = effect( () => { console.log(obj.foo) }, { onTrack } ) console.log('runner', runner) obj.foo++ console.log("events", events)
看下events的打印結果:
[ { effect: runner, // effect 函數的返回值 target: toRaw(obj), // 表示的是哪個響應式數據發生瞭變化 type: TrackOpTypes.GET, // 表示此次記錄操作的類型。 get 表示獲取值 key: 'foo' } ]
二、源碼分析
1、effect方法的實現
// packages/reactivity/src/effect.ts export interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean scheduler?: EffectScheduler scope?: EffectScope allowRecurse?: boolean onStop?: () => void } export function effect<T = any>( fn: () => T, // 副作用函數 options?: ReactiveEffectOptions // 結構如上 ): ReactiveEffectRunner { // 如果 fn 對象上有 effect 屬性 if ((fn as ReactiveEffectRunner).effect) { // 那麼就將 fn 替換為 fn.effect.fn fn = (fn as ReactiveEffectRunner).effect.fn } // 創建一個響應式副作用函數 const _effect = new ReactiveEffect(fn) if (options) { // 將配置項合並到響應式副作用函數上 extend(_effect, options) // 如果配置項中有 scope 屬性(該屬性的作用是指定副作用函數的作用域) if (options.scope) recordEffectScope(_effect, options.scope) } if (!options || !options.lazy) { // options.lazy 不為true _effect.run() // 執行響應式副作用函數 首次執行fn() } // _effect.run作用域綁定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // 將響應式副作用函數賦值給 runner.effect runner.effect = _effect return runner }
核心代碼:
創建一個響應式副作用函數const _effect = new ReactiveEffect(fn)
,其運行結果如下:
非lazy狀態執行響應式副作用函數_effect.run()
if (!options || !options.lazy) { // options.lazy 不為true _effect.run() // 執行響應式副作用函數 首次執行fn() }
_effect.run
作用域綁定到_effect
// _effect.run作用域綁定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
返回副作用函數runner
2、ReactiveEffect函數源碼
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 ) { // 記錄當前 ReactiveEffect 對象的作用域 recordEffectScope(this, scope) } run() { // 如果當前 ReactiveEffect 對象不處於活動狀態,直接返回 fn 的執行結果 if (!this.active) { return this.fn() } // 尋找當前 ReactiveEffect 對象的最頂層的父級作用域 let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack // 是否要跟蹤 while (parent) { if (parent === this) { return } parent = parent.parent } try { // 記錄父級作用域為當前活動的 ReactiveEffect 對象 this.parent = activeEffect activeEffect = this // 將當前活動的 ReactiveEffect 對象設置為 “自己” shouldTrack = true // 將 shouldTrack 設置為 true (表示是否需要收集依賴) // effectTrackDepth 用於標識當前的 effect 調用棧的深度,執行一次 effect 就會將 effectTrackDepth 加 1 trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { // 初始依賴追蹤標記 initDepMarkers(this) } else { // 清除依賴追蹤標記 cleanupEffect(this) } // 返回副作用函數執行結果 return this.fn() } finally { // 如果 effect調用棧的深度 沒有超過閾值 if (effectTrackDepth <= maxMarkerBits) { // 確定最終的依賴追蹤標記 finalizeDepMarkers(this) } // 執行完畢會將 effectTrackDepth 減 1 trackOpBit = 1 << --effectTrackDepth // 執行完畢,將當前活動的 ReactiveEffect 對象設置為 “父級作用域” activeEffect = this.parent // 將 shouldTrack 設置為上一個值 shouldTrack = lastShouldTrack // 將父級作用域設置為 undefined this.parent = undefined // 延時停止,這個標志是在 stop 方法中設置的 if (this.deferStop) { this.stop() } } } stop() { // stopped while running itself - defer the cleanup // 如果當前 活動的 ReactiveEffect 對象是 “自己” // 延遲停止,需要執行完當前的副作用函數之後再停止 if (activeEffect === this) { // 在 run 方法中會判斷 deferStop 的值,如果為 true,就會執行 stop 方法 this.deferStop = true } else if (this.active) {// 如果當前 ReactiveEffect 對象處於活動狀態 cleanupEffect(this) // 清除所有的依賴追蹤標記 if (this.onStop) { this.onStop() } this.active = false // 將 active 設置為 false } } }
- run方法的作用就是執行副作用函數,並且在執行副作用函數的過程中,會收集依賴;
- stop方法的作用就是停止當前的ReactiveEffect對象,停止之後,就不會再收集依賴瞭;
- activeEffect和this並不是每次都相等的,因為activeEffect會跟著調用棧的深度而變化,而this則是固定的;
三、依賴收集相關
1、如何觸發依賴收集
在副作用函數中, obj.count
就會觸發依賴收集
const runner = effect(() => { console.log(obj.count) })
觸發的入口在get攔截器裡面
function createGetter(isReadonly = false, shallow = false) { // 閉包返回 get 攔截器方法 return function get(target: Target, key: string | symbol, receiver: object) { // ... if (!isReadonly) { track(target, TrackOpTypes.GET, key) } // ... }
2、track源碼
const targetMap = new WeakMap(); /** * 收集依賴 * @param target target 觸發依賴的對象,例子中的obj * @param type 操作類型 比如obj.count就是get * @param key 指向對象的key, 比如obj.count就是count */ export function track(target: object, type: TrackOpTypes, key: unknown) { if (shouldTrack && activeEffect) { // 是否應該依賴收集 & 當前的new ReactiveEffect()即指向的就是當前正在執行的副作用函數 // 如果 targetMap 中沒有 target,就會創建一個 Map 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())) // createDep 生成dep = { w:0, n: 0} } const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) } }
shouldTrack在上面也講過,它的作用就是控制是否收集依賴;
activeEffect就是我們剛剛講的ReactiveEffect對象,它指向的就是當前正在執行的副作用函數;
track方法的作用就是收集依賴,它的實現非常簡單,就是在targetMap中記錄下target和key;
targetMap是一個WeakMap,它的鍵是target,值是一個Map,這個Map的鍵是key,值是一個Set;
targetMap的結構偽代碼如下:
targetMap = { target: { key: dep }, // 比如: obj: { count: { w: 0, n: 0 } } }
以上是最原始的depMap
dev環境為增加響應式調試會增加eventInfo
const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined
eventInfo結構如下:
trackEffects(dep, eventInfo)
如果 dep 中沒有當前的 ReactiveEffect 對象,就會添加進去, 作用就把對象的屬性操作與副作用函數建立關聯,接下來看trackEffects
3、trackEffects(dep, eventInfo)源碼解讀
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { // 執行之前 dep = Set(0) {w: 0, n: 0} // 執行之後 dep = Set(0) {w: 0, n: 2} dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. shouldTrack = !dep.has(activeEffect!) } if (shouldTrack) { // 將activeEffect添加到dep dep.add(activeEffect!) activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { // onTrack邏輯 activeEffect!.onTrack( extend( { effect: activeEffect! }, debuggerEventExtraInfo! ) ) } } }
dep.add(activeEffect!)
如果 dep 中沒有當前的 ReactiveEffect 對象,就會添加進去
最終生成的depTarget結構如下:
四、觸發依賴
比如例子中代碼obj.count++
就會觸發set攔截,觸發依賴更新
function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { //... const result = Reflect.set(target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) // 觸發ADD依賴更新 } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) //觸發SET依賴更新 } } //... }
1、trigger依賴更新
// 路徑:packages/reactivity/src/effect.ts export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { const depsMap = targetMap.get(target) // 獲取depsMap, targetMap是在track中創建的依賴 if (!depsMap) { // never been tracked return } let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target 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 { // 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)) { // 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) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } }
const depsMap = targetMap.get(target)
獲取 targetMap 中的 depsMap targetMap結構如下:
執行以上語句之後的depsMap結構如下:
將 depsMap 中 key 對應的 ReactiveEffect 對象添加到 deps 中deps.push(depsMap.get(key))
之後的deps結構如下:
triggerEffects(deps[0], eventInfo)
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]) } } }
trigger函數的作用就是觸發依賴,當我們修改數據的時候,就會觸發依賴,然後執行依賴中的副作用函數。
在這裡的實現其實並沒有執行,主要是收集一些需要執行的副作用函數,然後在丟給triggerEffects函數去執行,接下來看看triggerEffects函數。
2、triggerEffects(deps[0], eventInfo)
export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization 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) } } }
主要步驟
const effects = isArray(dep) ? dep : [...dep]
獲取effects
triggerEffect(effect, debuggerEventExtraInfo)
執行effect,接下來看看源碼
3、triggerEffect(effect, debuggerEventExtraInfo)
function triggerEffect( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { // 如果 effect.onTrigger 存在,就會執行,隻有開發模式下才會執行 if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } // 如果 effect 是一個調度器,就會執行 scheduler if (effect.scheduler) { effect.scheduler() } else { // 其它情況執行 effect.run() effect.run() } } }
effect.run()就是執行副作用函數
以上就是源碼分析Vue3響應式核心之effect的詳細內容,更多關於Vue3 effect的資料請關註WalkonNet其它相關文章!