源碼分析Vue3響應(yīng)式核心之effect
vue3響應(yīng)式核心文章匯總:
上篇vue3響應(yīng)式核心之reactive源碼詳解詳細(xì)講解了reactive的原理和實(shí)現(xiàn)源碼,本篇文章將講解effect源碼。
通常情況下我們是不會(huì)直接使用effect的,因?yàn)閑ffect是一個(gè)底層的API,在我們使用Vue3的時(shí)候Vue默認(rèn)會(huì)幫我們調(diào)用effect。 effect翻譯為作用,意思是使其發(fā)生作用,這個(gè)使其的其就是我們傳入的函數(shù),所以effect的作用就是讓我們傳入的函數(shù)發(fā)生作用,也就是執(zhí)行這個(gè)函數(shù)。 執(zhí)行過(guò)程簡(jiǎn)圖如下:

接下來(lái)先通過(guò)例子了解effect的基本用法,然后再去了解原理。
一、effect用法
1、基本用法
const obj = reactive({count: 1})
const runner = effect(() => {
console.log(obj.count)
})
obj.count++
結(jié)果會(huì)先打印1, 然后在obj.count++之后打印出2。
流程簡(jiǎn)圖如下:

運(yùn)行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(), 實(shí)際運(yùn)行的是
() => {
console.log(obj.count)
}
所以又打印出2
2、lazy屬性為true
此值為 true 時(shí),只有在第一次手動(dòng)調(diào)用 runner 后,依賴數(shù)據(jù)變更時(shí),才會(huì)自動(dòng)執(zhí)行 effect 的回調(diào),可以理解為 effect 的是在手動(dòng)調(diào)用 runner 后才首次執(zhí)行
const obj = reactive({count: 1})
const runner = effect(() => {
console.log(obj.count)
}, {
lazy: true
})
runner()
obj.count++
只會(huì)打印出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的打印結(jié)果:

[
{
effect: runner, // effect 函數(shù)的返回值
target: toRaw(obj), // 表示的是哪個(gè)響應(yīng)式數(shù)據(jù)發(fā)生了變化
type: TrackOpTypes.GET, // 表示此次記錄操作的類型。 get 表示獲取值
key: 'foo'
}
]二、源碼分析
1、effect方法的實(shí)現(xiàn)
// 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 對(duì)象上有 effect 屬性
if ((fn as ReactiveEffectRunner).effect) {
// 那么就將 fn 替換為 fn.effect.fn
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 創(chuàng)建一個(gè)響應(yīng)式副作用函數(shù)
const _effect = new ReactiveEffect(fn)
if (options) {
// 將配置項(xiàng)合并到響應(yīng)式副作用函數(shù)上
extend(_effect, options)
// 如果配置項(xiàng)中有 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)建一個(gè)響應(yīng)式副作用函數(shù)const _effect = new ReactiveEffect(fn),其運(yùn)行結(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
2、ReactiveEffect函數(shù)源碼
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = [] // 響應(yīng)式依賴項(xià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 對(duì)象的作用域
recordEffectScope(this, scope)
}
run() {
// 如果當(dāng)前 ReactiveEffect 對(duì)象不處于活動(dòng)狀態(tài),直接返回 fn 的執(zhí)行結(jié)果
if (!this.active) {
return this.fn()
}
// 尋找當(dāng)前 ReactiveEffect 對(duì)象的最頂層的父級(jí)作用域
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack // 是否要跟蹤
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
// 記錄父級(jí)作用域?yàn)楫?dāng)前活動(dòng)的 ReactiveEffect 對(duì)象
this.parent = activeEffect
activeEffect = this // 將當(dāng)前活動(dòng)的 ReactiveEffect 對(duì)象設(shè)置為 “自己”
shouldTrack = true // 將 shouldTrack 設(shè)置為 true (表示是否需要收集依賴)
// effectTrackDepth 用于標(biāo)識(shí)當(dāng)前的 effect 調(diào)用棧的深度,執(zhí)行一次 effect 就會(huì)將 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)用棧的深度 沒有超過(guò)閾值
if (effectTrackDepth <= maxMarkerBits) {
// 確定最終的依賴追蹤標(biāo)記
finalizeDepMarkers(this)
}
// 執(zhí)行完畢會(huì)將 effectTrackDepth 減 1
trackOpBit = 1 << --effectTrackDepth
// 執(zhí)行完畢,將當(dāng)前活動(dòng)的 ReactiveEffect 對(duì)象設(shè)置為 “父級(jí)作用域”
activeEffect = this.parent
// 將 shouldTrack 設(shè)置為上一個(gè)值
shouldTrack = lastShouldTrack
// 將父級(jí)作用域設(shè)置為 undefined
this.parent = undefined
// 延時(shí)停止,這個(gè)標(biāo)志是在 stop 方法中設(shè)置的
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
// 如果當(dāng)前 活動(dòng)的 ReactiveEffect 對(duì)象是 “自己”
// 延遲停止,需要執(zhí)行完當(dāng)前的副作用函數(shù)之后再停止
if (activeEffect === this) {
// 在 run 方法中會(huì)判斷 deferStop 的值,如果為 true,就會(huì)執(zhí)行 stop 方法
this.deferStop = true
} else if (this.active) {// 如果當(dāng)前 ReactiveEffect 對(duì)象處于活動(dòng)狀態(tài)
cleanupEffect(this) // 清除所有的依賴追蹤標(biāo)記
if (this.onStop) {
this.onStop()
}
this.active = false // 將 active 設(shè)置為 false
}
}
}- run方法的作用就是執(zhí)行副作用函數(shù),并且在執(zhí)行副作用函數(shù)的過(guò)程中,會(huì)收集依賴;
- stop方法的作用就是停止當(dāng)前的ReactiveEffect對(duì)象,停止之后,就不會(huì)再收集依賴了;
- activeEffect和this并不是每次都相等的,因?yàn)閍ctiveEffect會(huì)跟著調(diào)用棧的深度而變化,而this則是固定的;
三、依賴收集相關(guān)
1、如何觸發(fā)依賴收集
在副作用函數(shù)中, obj.count就會(huì)觸發(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)
}
// ...
}2、track源碼
const targetMap = new WeakMap();
/**
* 收集依賴
* @param target target 觸發(fā)依賴的對(duì)象,例子中的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,就會(huì)創(chuàng)建一個(gè) 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在上面也講過(guò),它的作用就是控制是否收集依賴;
activeEffect就是我們剛剛講的ReactiveEffect對(duì)象,它指向的就是當(dāng)前正在執(zhí)行的副作用函數(shù);
track方法的作用就是收集依賴,它的實(shí)現(xiàn)非常簡(jiǎn)單,就是在targetMap中記錄下target和key;
targetMap是一個(gè)WeakMap,它的鍵是target,值是一個(gè)Map,這個(gè)Map的鍵是key,值是一個(gè)Set;
targetMap的結(jié)構(gòu)偽代碼如下:
targetMap = {
target: {
key: dep
},
// 比如:
obj: {
count: {
w: 0,
n: 0
}
}
}
以上是最原始的depMap
dev環(huán)境為增加響應(yīng)式調(diào)試會(huì)增加eventInfo
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
eventInfo結(jié)構(gòu)如下:

trackEffects(dep, eventInfo)
如果 dep 中沒有當(dāng)前的 ReactiveEffect 對(duì)象,就會(huì)添加進(jìn)去, 作用就把對(duì)象的屬性操作與副作用函數(shù)建立關(guān)聯(lián),接下來(lái)看trackEffects
3、trackEffects(dep, eventInfo)源碼解讀
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 對(duì)象,就會(huì)添加進(jìn)去

最終生成的depTarget結(jié)構(gòu)如下:

四、觸發(fā)依賴
比如例子中代碼obj.count++就會(huì)觸發(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依賴更新
}
}
//...
}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中創(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í)行以上語(yǔ)句之后的depsMap結(jié)構(gòu)如下:

將 depsMap 中 key 對(duì)應(yīng)的 ReactiveEffect 對(duì)象添加到 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ù)的時(shí)候,就會(huì)觸發(fā)依賴,然后執(zhí)行依賴中的副作用函數(shù)。
在這里的實(shí)現(xiàn)其實(shí)并沒有執(zhí)行,主要是收集一些需要執(zhí)行的副作用函數(shù),然后在丟給triggerEffects函數(shù)去執(zhí)行,接下來(lái)看看triggerEffects函數(shù)。
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)執(zhí)行effect,接下來(lái)看看源碼
3、triggerEffect(effect, debuggerEventExtraInfo)
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
// 如果 effect.onTrigger 存在,就會(huì)執(zhí)行,只有開發(fā)模式下才會(huì)執(zhí)行
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
// 如果 effect 是一個(gè)調(diào)度器,就會(huì)執(zhí)行 scheduler
if (effect.scheduler) {
effect.scheduler()
} else {
// 其它情況執(zhí)行 effect.run()
effect.run()
}
}
}effect.run()就是執(zhí)行副作用函數(shù)
以上就是源碼分析Vue3響應(yīng)式核心之effect的詳細(xì)內(nèi)容,更多關(guān)于Vue3 effect的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue中的@blur事件 當(dāng)元素失去焦點(diǎn)時(shí)所觸發(fā)的事件問(wèn)題
這篇文章主要介紹了Vue中的@blur事件 當(dāng)元素失去焦點(diǎn)時(shí)所觸發(fā)的事件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
vue 導(dǎo)航內(nèi)容設(shè)置選中狀態(tài)樣式的例子
今天小編就為大家分享一篇vue 導(dǎo)航內(nèi)容設(shè)置選中狀態(tài)樣式的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
ant-design-vue table分頁(yè)onShowSizeChange后的pageNo解決
這篇文章主要介紹了ant-design-vue table分頁(yè)onShowSizeChange后的pageNo的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
解決vue項(xiàng)目路徑不正確,自動(dòng)跳轉(zhuǎn)404的問(wèn)題
這篇文章主要介紹了解決vue項(xiàng)目路徑不正確,自動(dòng)跳轉(zhuǎn)404的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10

