Vue3?計(jì)算屬性computed的實(shí)現(xiàn)原理
版本:3.2.31
computed 的函數(shù)簽名
// packages/reactivity/src/computed.ts // 只讀的 export function computed<T>( getter: ComputedGetter<T>, debugOptions?: DebuggerOptions ): ComputedRef<T> // 可寫(xiě)的 export function computed<T>( options: WritableComputedOptions<T>, debugOptions?: DebuggerOptions ): WritableComputedRef<T> export function computed<T>( getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>, debugOptions?: DebuggerOptions, isSSR = false )
上面的代碼為 computed 的函數(shù)重載。在第一個(gè)重載中,接受一個(gè) getter 函數(shù),并返回 ComputedRef 類(lèi)型的值。也就是說(shuō),在這種情況下,computed 接受一個(gè) getter 函數(shù),并根據(jù) getter 的返回值返回一個(gè)不可變的響應(yīng)式 ref 對(duì)象。
如下面的代碼所示:
const count = ref(1) // computed 接受一個(gè) getter 函數(shù) const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // 錯(cuò)誤
在第二個(gè)重載中,computed 函數(shù)接受一個(gè)具有 get 和 set 函數(shù)的 options 對(duì)象,并返回一個(gè)可寫(xiě)的 ref 對(duì)象。
如下面的代碼所示:
const count = ref(1)
const plusOne = computed({
// computed 函數(shù)接受一個(gè)具有 get 和 set 函數(shù)的 options 對(duì)象
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0第三個(gè)重載是第一個(gè)重載和第二個(gè)重載的結(jié)合,此時(shí) computed 函數(shù)既可以接受一個(gè) getter 函數(shù),又可以接受一個(gè)具有 get 和 set 函數(shù)的 options 對(duì)象。
computed 的實(shí)現(xiàn)
// packages/reactivity/src/computed.ts
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
// 判斷 getterOrOptions 參數(shù) 是否是一個(gè)函數(shù)
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
// getterOrOptions 是一個(gè)函數(shù),則將函數(shù)賦值給取值函數(shù)getter
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
// getterOrOptions 是一個(gè) options 選項(xiàng)對(duì)象,分別取 get/set 賦值給取值函數(shù)getter和賦值函數(shù)setter
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 實(shí)例化一個(gè) computed 實(shí)例
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
if (__DEV__ && debugOptions && !isSSR) {
cRef.effect.onTrack = debugOptions.onTrack
cRef.effect.onTrigger = debugOptions.onTrigger
}
return cRef as any
}在 computed 函數(shù)的實(shí)現(xiàn)中,首先判斷傳入的 getterOrOptions 參數(shù)是 getter 函數(shù)還是 options 對(duì)象。
如果 getterOrOptions 是 getter 函數(shù),則直接將傳入的參數(shù)賦值給 computed 的 getter 函數(shù)。由于這種情況下的計(jì)算屬性是只讀的,因此不允許設(shè)置 setter 函數(shù),并且在 DEV 環(huán)境中設(shè)置 setter 會(huì)報(bào)出警告。
如果 getterOrOptions 是 options 對(duì)象,則將該對(duì)象中的 get 、set 函數(shù)分別賦值給 computed 的 gettter 和 setter。
處理完 computed 的 getter 和 setter 后,則根據(jù) getter 和 setter 創(chuàng)建一個(gè) ComputedRefImpl 類(lèi)的實(shí)例,該實(shí)例是一個(gè) ref 對(duì)象,最后將該 ref 對(duì)象返回。
下面我們來(lái)看看 ComputedRefImpl 這個(gè)類(lèi)。
ComputedRefImpl 類(lèi)
// packages/reactivity/src/computed.ts
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
// value 用來(lái)緩存上一次計(jì)算的值
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean
// dirty標(biāo)志,用來(lái)表示是否需要重新計(jì)算值,為true 則意味著 臟, 需要計(jì)算
public _dirty = true
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
// getter的時(shí)候,不派發(fā)通知
if (!this._dirty) {
this._dirty = true
// 當(dāng)計(jì)算屬性依賴(lài)響應(yīng)式數(shù)據(jù)變化時(shí),手動(dòng)調(diào)用 triggerRefValue 函數(shù) 觸發(fā)響應(yīng)式
triggerRefValue(this)
}
})
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
// 獲取原始對(duì)象
const self = toRaw(this)
// 當(dāng)讀取 value 時(shí),手動(dòng)調(diào)用 trackRefValue 函數(shù)進(jìn)行追蹤
trackRefValue(self)
// 只有臟 才計(jì)算值,并將得到的值緩存到value中
if (self._dirty || !self._cacheable) {
// 將dirty設(shè)置為 false, 下一次訪問(wèn)直接使用緩存的 value中的值
self._dirty = false
self._value = self.effect.run()!
}
// 返回最新的值
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}緩存計(jì)算屬性,避免多次計(jì)算:
為了避免多次訪問(wèn)計(jì)算屬性時(shí)導(dǎo)致副作用函數(shù)多次執(zhí)行,在 ComputedRefImpl 類(lèi)中定義了一個(gè)私有變量 _value 和一個(gè)公共變量 _dirty。其中 _value 用來(lái)緩存上一次計(jì)算的值,_dirty 用來(lái)表示是否需要重新計(jì)算值,值為 true 時(shí)意味著「臟」, 則計(jì)算屬性需要重新計(jì)算。在讀取計(jì)算屬性時(shí),會(huì)觸發(fā) getter 函數(shù),在 getter 函數(shù)中,判斷 _dirty 的值是否為 true,如果是,才重新執(zhí)行副作用,將執(zhí)行結(jié)果緩存到 _value 變量中,并返回最新的值。如果_dirty 的值為 false,說(shuō)明計(jì)算屬性不需要重新計(jì)算,返回上一次計(jì)算的結(jié)果即可。
數(shù)據(jù)變化,計(jì)算屬性需重新計(jì)算:
當(dāng)計(jì)算屬性的依賴(lài)數(shù)據(jù)發(fā)生變化時(shí),為了使得計(jì)算屬性是最新的,Vue 在 ComputedRefImpl 類(lèi)的構(gòu)造函數(shù)中為 getter 創(chuàng)建了一個(gè)副作用函數(shù)。在該副作用函數(shù)中,判斷 this._dirty 標(biāo)記是否為 false,如果是,則將 this._dirty 置為 true,當(dāng)下一次訪問(wèn)計(jì)算屬性時(shí),就會(huì)重新執(zhí)行副作用函數(shù)計(jì)算值。
計(jì)算屬性中的 effect 嵌套:
當(dāng)我們?cè)诹硪粋€(gè) effect 中讀取計(jì)算屬性的值時(shí),如下面代碼所示:
const sumResult = computed(() => obj.foo + obj.bar)
effect(() => {
// 在該副作用函數(shù)中讀取 sumResult.value
console.log(sumResult.value)
})
// 修改 obj.bar 的值
obj.bar++如上面的代碼所示,sumResult 是一個(gè)計(jì)算屬性,并且在另一個(gè) effect 的副作用函數(shù)中讀取了 sumResult.value 的值。如果此時(shí)修改了 obj.bar 的值,期望的結(jié)果是副作用函數(shù)重新執(zhí)行,但實(shí)際上并未重新觸發(fā)副作用函數(shù)執(zhí)行。
在一個(gè) effect 中讀取計(jì)算屬性的值,其本質(zhì)上就是一個(gè)典型的 effect 嵌套。一個(gè)計(jì)算屬性?xún)?nèi)部擁有自己的 effect ,并且它是懶執(zhí)行的,只有當(dāng)真正讀取計(jì)算屬性的值時(shí)才會(huì)執(zhí)行。當(dāng)把計(jì)算屬性用于另外一個(gè) effect 時(shí),就會(huì)發(fā)生 effect 嵌套,外層的 effect 不會(huì)被內(nèi)層 effect 中的響應(yīng)式數(shù)據(jù)收集。因此,當(dāng)讀取計(jì)算屬性的值時(shí),需要手動(dòng)調(diào)用 trackRefValue 函數(shù)進(jìn)行追蹤,當(dāng)計(jì)算屬性依賴(lài)的響應(yīng)式數(shù)據(jù)發(fā)生變化時(shí),手動(dòng)調(diào)用 triggerRefValue 函數(shù)觸發(fā)響應(yīng)。
總結(jié)
computed 的實(shí)現(xiàn),它實(shí)際上就是一個(gè)懶執(zhí)行的副作用函數(shù),通過(guò) _dirty 標(biāo)志使得副作用函數(shù)可以懶執(zhí)行。dirty 標(biāo)志用來(lái)表示是否需要重新計(jì)算值,當(dāng)值為 true 時(shí)意味著「臟」, 則計(jì)算屬性需要重新計(jì)算,即重新執(zhí)行副作用。
到此這篇關(guān)于Vue3 計(jì)算屬性computed的實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Vue3 computed實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
動(dòng)態(tài)實(shí)現(xiàn)element ui的el-table某列數(shù)據(jù)不同樣式的示例
這篇文章主要介紹了動(dòng)態(tài)實(shí)現(xiàn)element ui的el-table某列數(shù)據(jù)不同樣式的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
在vue中把含有html標(biāo)簽轉(zhuǎn)為html渲染頁(yè)面的實(shí)例
今天小編就為大家分享一篇在vue中把含有html標(biāo)簽轉(zhuǎn)為html渲染頁(yè)面的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-10-10
VUE項(xiàng)目初建和常見(jiàn)問(wèn)題總結(jié)
在本篇文章里小編給大家整理的是關(guān)于VUE 項(xiàng)目初建和常見(jiàn)問(wèn)題以及相關(guān)知識(shí)點(diǎn)內(nèi)容,有需要的朋友們學(xué)習(xí)下。2019-09-09
ant-design-vue導(dǎo)航菜單a-menu的使用解讀
這篇文章主要介紹了ant-design-vue導(dǎo)航菜單a-menu的使用解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
vue頁(yè)面切換項(xiàng)目實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的方法
這篇文章主要介紹了vue頁(yè)面切換項(xiàng)目實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
vue實(shí)現(xiàn)簡(jiǎn)單滑塊驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)簡(jiǎn)單滑塊驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
vue實(shí)戰(zhàn)中的一些實(shí)用小魔法匯總
這篇文章主要給大家介紹了關(guān)于vue實(shí)戰(zhàn)中一些實(shí)用小魔法的相關(guān)資料,這些技巧和竅門(mén),可以幫助你成為更好的Vue開(kāi)發(fā)人員,需要的朋友可以參考下2021-06-06
微信小程序開(kāi)發(fā)實(shí)現(xiàn)消息框彈出
在小程序的wxml文件中創(chuàng)建消息框,消息框一般包含要提示的消息內(nèi)容以及確認(rèn)和取消按鈕,在小程序的wxss文件中定義消息框的樣式,在小程序的js文件中,我們需要通過(guò)Animation對(duì)象實(shí)現(xiàn)消息框的彈出動(dòng)畫(huà)2023-12-12
詳解Vue 動(dòng)態(tài)組件與全局事件綁定總結(jié)
這篇文章主要介紹了詳解Vue 動(dòng)態(tài)組件與全局事件綁定總結(jié),從示例中發(fā)現(xiàn)并解決問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11

