Vue3?計算屬性computed的實現原理
版本:3.2.31
computed 的函數簽名
// packages/reactivity/src/computed.ts // 只讀的 export function computed<T>( getter: ComputedGetter<T>, debugOptions?: DebuggerOptions ): ComputedRef<T> // 可寫的 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 的函數重載。在第一個重載中,接受一個 getter 函數,并返回 ComputedRef 類型的值。也就是說,在這種情況下,computed 接受一個 getter 函數,并根據 getter 的返回值返回一個不可變的響應式 ref 對象。
如下面的代碼所示:
const count = ref(1) // computed 接受一個 getter 函數 const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 2 plusOne.value++ // 錯誤
在第二個重載中,computed 函數接受一個具有 get 和 set 函數的 options 對象,并返回一個可寫的 ref 對象。
如下面的代碼所示:
const count = ref(1)
const plusOne = computed({
// computed 函數接受一個具有 get 和 set 函數的 options 對象
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0第三個重載是第一個重載和第二個重載的結合,此時 computed 函數既可以接受一個 getter 函數,又可以接受一個具有 get 和 set 函數的 options 對象。
computed 的實現
// 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 參數 是否是一個函數
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
// getterOrOptions 是一個函數,則將函數賦值給取值函數getter
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
// getterOrOptions 是一個 options 選項對象,分別取 get/set 賦值給取值函數getter和賦值函數setter
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 實例化一個 computed 實例
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 函數的實現中,首先判斷傳入的 getterOrOptions 參數是 getter 函數還是 options 對象。
如果 getterOrOptions 是 getter 函數,則直接將傳入的參數賦值給 computed 的 getter 函數。由于這種情況下的計算屬性是只讀的,因此不允許設置 setter 函數,并且在 DEV 環(huán)境中設置 setter 會報出警告。
如果 getterOrOptions 是 options 對象,則將該對象中的 get 、set 函數分別賦值給 computed 的 gettter 和 setter。
處理完 computed 的 getter 和 setter 后,則根據 getter 和 setter 創(chuàng)建一個 ComputedRefImpl 類的實例,該實例是一個 ref 對象,最后將該 ref 對象返回。
下面我們來看看 ComputedRefImpl 這個類。
ComputedRefImpl 類
// packages/reactivity/src/computed.ts
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
// value 用來緩存上一次計算的值
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean
// dirty標志,用來表示是否需要重新計算值,為true 則意味著 臟, 需要計算
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的時候,不派發(fā)通知
if (!this._dirty) {
this._dirty = true
// 當計算屬性依賴響應式數據變化時,手動調用 triggerRefValue 函數 觸發(fā)響應式
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
// 獲取原始對象
const self = toRaw(this)
// 當讀取 value 時,手動調用 trackRefValue 函數進行追蹤
trackRefValue(self)
// 只有臟 才計算值,并將得到的值緩存到value中
if (self._dirty || !self._cacheable) {
// 將dirty設置為 false, 下一次訪問直接使用緩存的 value中的值
self._dirty = false
self._value = self.effect.run()!
}
// 返回最新的值
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}緩存計算屬性,避免多次計算:
為了避免多次訪問計算屬性時導致副作用函數多次執(zhí)行,在 ComputedRefImpl 類中定義了一個私有變量 _value 和一個公共變量 _dirty。其中 _value 用來緩存上一次計算的值,_dirty 用來表示是否需要重新計算值,值為 true 時意味著「臟」, 則計算屬性需要重新計算。在讀取計算屬性時,會觸發(fā) getter 函數,在 getter 函數中,判斷 _dirty 的值是否為 true,如果是,才重新執(zhí)行副作用,將執(zhí)行結果緩存到 _value 變量中,并返回最新的值。如果_dirty 的值為 false,說明計算屬性不需要重新計算,返回上一次計算的結果即可。
數據變化,計算屬性需重新計算:
當計算屬性的依賴數據發(fā)生變化時,為了使得計算屬性是最新的,Vue 在 ComputedRefImpl 類的構造函數中為 getter 創(chuàng)建了一個副作用函數。在該副作用函數中,判斷 this._dirty 標記是否為 false,如果是,則將 this._dirty 置為 true,當下一次訪問計算屬性時,就會重新執(zhí)行副作用函數計算值。
計算屬性中的 effect 嵌套:
當我們在另一個 effect 中讀取計算屬性的值時,如下面代碼所示:
const sumResult = computed(() => obj.foo + obj.bar)
effect(() => {
// 在該副作用函數中讀取 sumResult.value
console.log(sumResult.value)
})
// 修改 obj.bar 的值
obj.bar++如上面的代碼所示,sumResult 是一個計算屬性,并且在另一個 effect 的副作用函數中讀取了 sumResult.value 的值。如果此時修改了 obj.bar 的值,期望的結果是副作用函數重新執(zhí)行,但實際上并未重新觸發(fā)副作用函數執(zhí)行。
在一個 effect 中讀取計算屬性的值,其本質上就是一個典型的 effect 嵌套。一個計算屬性內部擁有自己的 effect ,并且它是懶執(zhí)行的,只有當真正讀取計算屬性的值時才會執(zhí)行。當把計算屬性用于另外一個 effect 時,就會發(fā)生 effect 嵌套,外層的 effect 不會被內層 effect 中的響應式數據收集。因此,當讀取計算屬性的值時,需要手動調用 trackRefValue 函數進行追蹤,當計算屬性依賴的響應式數據發(fā)生變化時,手動調用 triggerRefValue 函數觸發(fā)響應。
總結
computed 的實現,它實際上就是一個懶執(zhí)行的副作用函數,通過 _dirty 標志使得副作用函數可以懶執(zhí)行。dirty 標志用來表示是否需要重新計算值,當值為 true 時意味著「臟」, 則計算屬性需要重新計算,即重新執(zhí)行副作用。
到此這篇關于Vue3 計算屬性computed的實現原理的文章就介紹到這了,更多相關Vue3 computed實現內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue全局共享數據之globalData,vuex,本地存儲的使用
這篇文章主要介紹了Vue全局共享數據之globalData,vuex,本地存儲的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
詳解基于vue-router的動態(tài)權限控制實現方案
本篇文章主要介紹了詳解基于vue-router的動態(tài)權限實現方案,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
vite+vue3中require?is?not?defined問題及解決
這篇文章主要介紹了vite+vue3中require?is?not?defined問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
vue3中項目優(yōu)化方法詳解(Web?Worker的使用)
最近在做vue3的項目中,遇到了計算量龐大導致頁面響應緩慢的問題,所以下面這篇文章主要給大家介紹了關于vue3中項目優(yōu)化方法的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-07-07
vue實現樣式之間的切換及vue動態(tài)樣式的實現方法
這篇文章主要介紹了vue中如何實現樣式之間的切換及vue動態(tài)樣式的實現方法,本文給大家介紹的非常詳細,具有參考借鑒價值,需要的朋友可以參考下2017-12-12
詳解如何創(chuàng)建并發(fā)布一個 vue 組件
這篇文章主要介紹了詳解如何創(chuàng)建并發(fā)布一個vue組件,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11

