vue3中effect函數(shù)到底是什么詳解
前言
vue3中的effect函數(shù)是響應(yīng)式的核心,它被稱作副作用函數(shù),那么什么是副作用函數(shù)呢?我們來看一下。
vue中,數(shù)據(jù)target改變,可能會引起其他數(shù)據(jù)或者視圖發(fā)生變化,那么,其他數(shù)據(jù)改變以及視圖改變這些效果就是target改變的副作用。像watch、computed、這些都是會產(chǎn)生副作用的函數(shù),它們的底層都是使用了effect。
我們先來看看effect的原理。首先先來認識幾個重要的全局變量。
targetMap
targetMap是一個WeakMap,存儲了{target -> key -> dep}的關(guān)系。targetMap的key是需要做響應(yīng)式處理的原始對象,targetMap的value是一個Map,Map的key是原始對象的屬性,Map的value是每個屬性關(guān)聯(lián)的副作用函數(shù)Set。副作用函數(shù)就是我們需要追蹤的依賴,也就是訂閱者。
activeEffect
activeEffect保存當前正在執(zhí)行的副作用函數(shù),它是一個對象,effect的類型如下:
let activeEffect: ReactiveEffect | undefined
// effect類型
class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
parent: ReactiveEffect | undefined = undefined
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run(){}
stop() {}
}shouldTrack
shouldTrack 變量用來標識是否開啟依賴搜集,只有 shouldTrack 的值為 true 時,才進行依賴收集,即將副作用函數(shù)添加到依賴集合中。初始化值是true。當執(zhí)行run()時,會將shouldTrack設(shè)置為true,開啟依賴收集。
effect
接下來看看副作用函數(shù)effect的實現(xiàn)原理。當依賴的數(shù)據(jù)變化的時候副作用函數(shù)便會觸發(fā),想當然的肯定會涉及到依賴收集收集與追蹤,因此之后我們還會講解一下track、trigger與effect的關(guān)系。
interface ReactiveEffectRunner<T = any> {
(): T
// ReactiveEffect就是上面提到的activeEffect的類型
effect: ReactiveEffect
}
function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
// 如果傳入的fn本身就是effect,那么就直接執(zhí)行 effect的副作用
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 將fn包裝成effect
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
// 不是延遲執(zhí)行,直接執(zhí)行副作用函數(shù)
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}fn就是傳給effect的副作用,將fn傳給ReactiveEffect包裝一下,將其包裝成標準的effect類型。標準的effect類型是ReactiveEffect類,具有active、deps、deps、run()、stop()這些屬性方法。其中run方法中就是執(zhí)行傳入的副作用函數(shù)fn。 但是effect函數(shù)返回的不是ReactiveEffect類型,而是ReactiveEffectRunner類型,接口ReactiveEffectRunner中具有effect屬性,它是ReactiveEffect類型,因此effect最終需要runner.effect = _effect,然后返回runner。
我們傳入的副作用在effect的run()中執(zhí)行。需要重點看一下run。
run() {
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
this.parent = activeEffect
// 全局activeEffect指向當前執(zhí)行的自身
activeEffect = this
// 開啟依賴收集
shouldTrack = true
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
cleanupEffect(this)
}
// 執(zhí)行副作用函數(shù)
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
if (this.deferStop) {
this.stop()
}
}
}執(zhí)行run的時候,將全局activeEffect指向自身,也就是當前執(zhí)行的effect,然后開啟依賴收集標識位,執(zhí)行副作用函數(shù)。
說到這,我們好像還沒發(fā)現(xiàn)effect與track和trigger有什么關(guān)系。track、trigger函數(shù)與effect函數(shù)都是位于源碼同一個文件下的,那么它們肯定是有關(guān)聯(lián)的。接下來,我們看看track的邏輯。
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)
}
}我們知道,在研究vue的雙向數(shù)據(jù)綁定時,需要進行依賴收集與依賴追蹤。track就是依賴收集的過程,當我們通過響應(yīng)式API初始化數(shù)據(jù)或者模板中渲染一些變量的時候,就需要進行依賴收集,也就是track。依賴,也就是我們的副作用函數(shù),依賴收集的過程其實就是收集副作用函數(shù)的過程。而副作用,就是我們剛剛上文講的effect。這就關(guān)聯(lián)上了。 剛剛研究effect的時候,提到了一些全局變量,其中targetMap存儲了{target -> key -> dep}的關(guān)系,我們依賴收集的過程,就是將副作用存儲起來的過程。因此在track中,傳入的參數(shù)target對應(yīng)的就是targetMap的key。先查看全局變量targetMap中是否存在該target,沒有的話就初始化map賦值。同理,通過key來尋找最終的副作用Set,沒有的話就初始化設(shè)置。至此,我們完善了{target -> key -> dep}的關(guān)系,接下來看看 trackEffects。
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!)
}
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack({
effect: activeEffect!,
...debuggerEventExtraInfo!
})
}
}
}trackEffects的邏輯其實就是將當前激活的activeEffect放到dep中,這樣就完成了依賴收集過程。 trigger過程就不多贅述了,就是根據(jù){target -> key -> dep}的關(guān)系一層一層的找到最后的dep,然后執(zhí)行其中的副作用函數(shù)即可。
說到這,可能還是有人不明白,如果不使用watch、computed這些帶有副作用的 api, track 、trigger與effect又有什么關(guān)系呢?那track收集的副作用又是什么呢?
說到這里我想說,關(guān)于副作用,除了watch、computed這些api中用戶手動傳入的fn算作副作用,頁面渲染也是副作用。我們使用vue中雙向數(shù)據(jù)綁定的特性,意味著當我們定義一個響應(yīng)式變量,如果模板中使用了這個響應(yīng)式變量,那么這個變量在模板中的渲染是響應(yīng)式的,這個渲染是副作用,那么這個頁面渲染就需要被收集到dep中。如果這個變量還是作為 watch中fn的內(nèi)部出現(xiàn)的,那么這個變量的副作用又多了一個,還需要被額外收集。所以dep是一個Set的數(shù)據(jù)結(jié)構(gòu)。當這個變量變化的時候,頁面要重新渲染,watch也要重新計算,這就是trigger的過程。
當然這些針對的是響應(yīng)式數(shù)據(jù),如果我們僅僅定義一個靜態(tài)數(shù)據(jù),那么是不需要進行依賴收集的,它也不是響應(yīng)式的。 說到這里應(yīng)該可以明白,effect與響應(yīng)式密切相關(guān)了。
computed
講完effect,我們不難知道computed、watch也是一種effect,只是參數(shù)不同而已。我們簡單看一下computed。
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
// computed也是實例化effect
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}從computed的構(gòu)造器中,我們可以看到computed也是實例化effect實現(xiàn)的,只是傳的參數(shù)不同而已,我們暫且稱作computedEffect??梢钥吹絚omputedEffect的computed屬性是true。
watch
注意:watch雖然依賴于effect函數(shù),但是在源碼目錄中卻不屬于reactivity目錄,而是runtime-core目錄下的。
const effect = new ReactiveEffect(getter, scheduler)
可以看到watch中同樣使用ReactiveEffect創(chuàng)建副作用,只不過多傳了scheduler參數(shù)。
總結(jié)
到此這篇關(guān)于vue3中effect函數(shù)到底是什么的文章就介紹到這了,更多相關(guān)vue3中effect函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ant-design-vue前端UI庫,如何解決Table中時間格式化問題
這篇文章主要介紹了ant-design-vue前端UI庫,如何解決Table中時間格式化問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
Vue中傳遞自定義參數(shù)到后端、后端獲取數(shù)據(jù)并使用Map接收參數(shù)
有些傳遞的參數(shù)是直接拼接到URL地址欄中的、但是為了統(tǒng)一管理、不能將傳遞的參數(shù)直接拼接到地址欄中,接下來通過本文給大家介紹Vue中傳遞自定義參數(shù)到后端、后端獲取數(shù)據(jù)并使用Map接收參數(shù),感興趣的朋友一起看看吧2022-10-10
vue中watch和computed為什么能監(jiān)聽到數(shù)據(jù)的改變以及不同之處
這篇文章主要介紹了vue中watch和computed為什么能監(jiān)聽到數(shù)據(jù)的改變以及不同之處,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12
vue 頁面回退mounted函數(shù)不執(zhí)行的解決方案
這篇文章主要介紹了vue 頁面回退mounted函數(shù)不執(zhí)行的解決方案 ,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07

