您好,登錄后才能下訂單哦!
這篇文章主要講解了“Vue3響應(yīng)式核心之effect怎么使用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Vue3響應(yīng)式核心之effect怎么使用”吧!
通常情況下我們是不會直接使用effect的,因為effect是一個底層的API,在我們使用Vue3的時候Vue默認會幫我們調(diào)用effect。 effect翻譯為作用,意思是使其發(fā)生作用,這個使其的其就是我們傳入的函數(shù),所以effect的作用就是讓我們傳入的函數(shù)發(fā)生作用,也就是執(zhí)行這個函數(shù)。 執(zhí)行過程簡圖如下:
接下來先通過例子了解effect的基本用法,然后再去了解原理。
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }) obj.count++
結(jié)果會先打印1, 然后在obj.count++
之后打印出2。
流程簡圖如下:
運行effect(fun)
// 先執(zhí)行 fun() // 打印出1 const runner = new ReactiveEffect(fn) return runner runner: { run() { this.fun() //執(zhí)行fun }, stop() { } }
console.log(obj.count)
track依賴收集 結(jié)構(gòu)如下:
obj.count++
觸發(fā)依賴,執(zhí)行runner.run(), 實際運行的是
() => { console.log(obj.count) }
所以又打印出2
此值為 true 時,只有在第一次手動調(diào)用 runner 后,依賴數(shù)據(jù)變更時,才會自動執(zhí)行 effect 的回調(diào),可以理解為 effect 的是在手動調(diào)用 runner 后才首次執(zhí)行
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }, { lazy: true }) runner() obj.count++
只會打印出2
原因是effect源碼中有如下邏輯:
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的打印結(jié)果:
[ { effect: runner, // effect 函數(shù)的返回值 target: toRaw(obj), // 表示的是哪個響應(yīng)式數(shù)據(jù)發(fā)生了變化 type: TrackOpTypes.GET, // 表示此次記錄操作的類型。 get 表示獲取值 key: 'foo' } ]
// 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, // 副作用函數(shù) options?: ReactiveEffectOptions // 結(jié)構(gòu)如上 ): ReactiveEffectRunner { // 如果 fn 對象上有 effect 屬性 if ((fn as ReactiveEffectRunner).effect) { // 那么就將 fn 替換為 fn.effect.fn fn = (fn as ReactiveEffectRunner).effect.fn } // 創(chuàng)建一個響應(yīng)式副作用函數(shù) const _effect = new ReactiveEffect(fn) if (options) { // 將配置項合并到響應(yīng)式副作用函數(shù)上 extend(_effect, options) // 如果配置項中有 scope 屬性(該屬性的作用是指定副作用函數(shù)的作用域) if (options.scope) recordEffectScope(_effect, options.scope) } if (!options || !options.lazy) { // options.lazy 不為true _effect.run() // 執(zhí)行響應(yīng)式副作用函數(shù) 首次執(zhí)行fn() } // _effect.run作用域綁定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // 將響應(yīng)式副作用函數(shù)賦值給 runner.effect runner.effect = _effect return runner }
核心代碼:
創(chuàng)建一個響應(yīng)式副作用函數(shù)const _effect = new ReactiveEffect(fn)
,其運行結(jié)果如下:
非lazy狀態(tài)執(zhí)行響應(yīng)式副作用函數(shù)_effect.run()
if (!options || !options.lazy) { // options.lazy 不為true _effect.run() // 執(zhí)行響應(yīng)式副作用函數(shù) 首次執(zhí)行fn() }
_effect.run
作用域綁定到_effect
// _effect.run作用域綁定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
返回副作用函數(shù)runner
export class ReactiveEffect<T = any> { active = true deps: Dep[] = [] // 響應(yīng)式依賴項的集合 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 ) { // 記錄當(dāng)前 ReactiveEffect 對象的作用域 recordEffectScope(this, scope) } run() { // 如果當(dāng)前 ReactiveEffect 對象不處于活動狀態(tài),直接返回 fn 的執(zhí)行結(jié)果 if (!this.active) { return this.fn() } // 尋找當(dāng)前 ReactiveEffect 對象的最頂層的父級作用域 let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack // 是否要跟蹤 while (parent) { if (parent === this) { return } parent = parent.parent } try { // 記錄父級作用域為當(dāng)前活動的 ReactiveEffect 對象 this.parent = activeEffect activeEffect = this // 將當(dāng)前活動的 ReactiveEffect 對象設(shè)置為 “自己” shouldTrack = true // 將 shouldTrack 設(shè)置為 true (表示是否需要收集依賴) // effectTrackDepth 用于標(biāo)識當(dāng)前的 effect 調(diào)用棧的深度,執(zhí)行一次 effect 就會將 effectTrackDepth 加 1 trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { // 初始依賴追蹤標(biāo)記 initDepMarkers(this) } else { // 清除依賴追蹤標(biāo)記 cleanupEffect(this) } // 返回副作用函數(shù)執(zhí)行結(jié)果 return this.fn() } finally { // 如果 effect調(diào)用棧的深度 沒有超過閾值 if (effectTrackDepth <= maxMarkerBits) { // 確定最終的依賴追蹤標(biāo)記 finalizeDepMarkers(this) } // 執(zhí)行完畢會將 effectTrackDepth 減 1 trackOpBit = 1 << --effectTrackDepth // 執(zhí)行完畢,將當(dāng)前活動的 ReactiveEffect 對象設(shè)置為 “父級作用域” activeEffect = this.parent // 將 shouldTrack 設(shè)置為上一個值 shouldTrack = lastShouldTrack // 將父級作用域設(shè)置為 undefined this.parent = undefined // 延時停止,這個標(biāo)志是在 stop 方法中設(shè)置的 if (this.deferStop) { this.stop() } } } stop() { // stopped while running itself - defer the cleanup // 如果當(dāng)前 活動的 ReactiveEffect 對象是 “自己” // 延遲停止,需要執(zhí)行完當(dāng)前的副作用函數(shù)之后再停止 if (activeEffect === this) { // 在 run 方法中會判斷 deferStop 的值,如果為 true,就會執(zhí)行 stop 方法 this.deferStop = true } else if (this.active) {// 如果當(dāng)前 ReactiveEffect 對象處于活動狀態(tài) cleanupEffect(this) // 清除所有的依賴追蹤標(biāo)記 if (this.onStop) { this.onStop() } this.active = false // 將 active 設(shè)置為 false } } }
run方法的作用就是執(zhí)行副作用函數(shù),并且在執(zhí)行副作用函數(shù)的過程中,會收集依賴;
stop方法的作用就是停止當(dāng)前的ReactiveEffect對象,停止之后,就不會再收集依賴了;
activeEffect和this并不是每次都相等的,因為activeEffect會跟著調(diào)用棧的深度而變化,而this則是固定的;
在副作用函數(shù)中, obj.count
就會觸發(fā)依賴收集
const runner = effect(() => { console.log(obj.count) })
觸發(fā)的入口在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) } // ... }
const targetMap = new WeakMap(); /** * 收集依賴 * @param target target 觸發(fā)依賴的對象,例子中的obj * @param type 操作類型 比如obj.count就是get * @param key 指向?qū)ο蟮膋ey, 比如obj.count就是count */ export function track(target: object, type: TrackOpTypes, key: unknown) { if (shouldTrack && activeEffect) { // 是否應(yīng)該依賴收集 & 當(dāng)前的new ReactiveEffect()即指向的就是當(dāng)前正在執(zhí)行的副作用函數(shù) // 如果 targetMap 中沒有 target,就會創(chuàng)建一個 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對象,它指向的就是當(dāng)前正在執(zhí)行的副作用函數(shù);
track方法的作用就是收集依賴,它的實現(xiàn)非常簡單,就是在targetMap中記錄下target和key;
targetMap是一個WeakMap,它的鍵是target,值是一個Map,這個Map的鍵是key,值是一個Set;
targetMap的結(jié)構(gòu)偽代碼如下:
targetMap = { target: { key: dep }, // 比如: obj: { count: { w: 0, n: 0 } } }
以上是最原始的depMap
dev環(huán)境為增加響應(yīng)式調(diào)試會增加eventInfo
const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined
eventInfo結(jié)構(gòu)如下:
trackEffects(dep, eventInfo)
如果 dep 中沒有當(dāng)前的 ReactiveEffect 對象,就會添加進去, 作用就把對象的屬性操作與副作用函數(shù)建立關(guān)聯(lián),接下來看trackEffects
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { // 執(zhí)行之前 dep = Set(0) {w: 0, n: 0} // 執(zhí)行之后 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 中沒有當(dāng)前的 ReactiveEffect 對象,就會添加進去
最終生成的depTarget結(jié)構(gòu)如下:
比如例子中代碼obj.count++
就會觸發(fā)set攔截,觸發(fā)依賴更新
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) // 觸發(fā)ADD依賴更新 } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) //觸發(fā)SET依賴更新 } } //... }
// 路徑: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中創(chuàng)建的依賴 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結(jié)構(gòu)如下:
執(zhí)行以上語句之后的depsMap結(jié)構(gòu)如下:
將 depsMap 中 key 對應(yīng)的 ReactiveEffect 對象添加到 deps 中deps.push(depsMap.get(key))
之后的deps結(jié)構(gòu)如下:
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函數(shù)的作用就是觸發(fā)依賴,當(dāng)我們修改數(shù)據(jù)的時候,就會觸發(fā)依賴,然后執(zhí)行依賴中的副作用函數(shù)。
在這里的實現(xiàn)其實并沒有執(zhí)行,主要是收集一些需要執(zhí)行的副作用函數(shù),然后在丟給triggerEffects函數(shù)去執(zhí)行,接下來看看triggerEffects函數(shù)。
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)
執(zhí)行effect,接下來看看源碼
function triggerEffect( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { // 如果 effect.onTrigger 存在,就會執(zhí)行,只有開發(fā)模式下才會執(zhí)行 if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } // 如果 effect 是一個調(diào)度器,就會執(zhí)行 scheduler if (effect.scheduler) { effect.scheduler() } else { // 其它情況執(zhí)行 effect.run() effect.run() } } }
effect.run()就是執(zhí)行副作用函數(shù)
感謝各位的閱讀,以上就是“Vue3響應(yīng)式核心之effect怎么使用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Vue3響應(yīng)式核心之effect怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。