計(jì)算屬性
計(jì)算屬性 API: computed
例子
import { computed, ref } from 'vue'
const count = ref(1)
const result = computed(() => count.value + 1)
console.log(result.value) // 2
result.value++ // error
count.value++
console.log(result.value) // 3
- 先使用
ref API
創(chuàng)建了一個(gè)響應(yīng)式對象count
- 再使用
computed API
創(chuàng)建了另一個(gè)響應(yīng)式對象result
- 修改
count.value
的時(shí)候敢会,result.value
就會(huì)自動(dòng)發(fā)生變化 - 直接修改
result.value
會(huì)報(bào)一個(gè)錯(cuò)誤- 因?yàn)槿绻覀儌鬟f給
computed
的是一個(gè)函數(shù)毒涧,那么這就是一個(gè)getter
函數(shù)钾菊,我們只能獲取它的值扶欣,而不能直接修改它 - 也可以給
computed
傳入一個(gè)對象吠各,達(dá)到修改的目的
- 因?yàn)槿绻覀儌鬟f給
const result = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
computed API 的實(shí)現(xiàn)
function computed(getterOrOptions) {
// getter 函數(shù)
let getter
// setter 函數(shù)
let setter
// 標(biāo)準(zhǔn)化參數(shù)
if (isFunction(getterOrOptions)) {
// 表面?zhèn)魅氲氖?getter 函數(shù)蕴轨,不能修改計(jì)算屬性的值
getter = getterOrOptions
setter = (process.env.NODE_ENV !== 'production')
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
}
else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 數(shù)據(jù)是否臟的
let dirty = true
// 計(jì)算結(jié)果
let value
let computed
// 創(chuàng)建副作用函數(shù)
const runner = effect(getter, {
// 延時(shí)執(zhí)行
lazy: true,
// 標(biāo)記這是一個(gè) computed effect 用于在 trigger 階段的優(yōu)先級排序
computed: true,
// 調(diào)度執(zhí)行的實(shí)現(xiàn)
scheduler: () => {
if (!dirty) {
dirty = true
// 派發(fā)通知唠雕,通知運(yùn)行訪問該計(jì)算屬性的 activeEffect
trigger(computed, "set" /* SET */, 'value')
}
}
})
// 創(chuàng)建 computed 對象
computed = {
__v_isRef: true,
// 暴露 effect 對象以便計(jì)算屬性可以停止計(jì)算
effect: runner,
get value() {
// 計(jì)算屬性的 getter
if (dirty) {
// 只有數(shù)據(jù)為臟的時(shí)候才會(huì)重新計(jì)算
value = runner()
dirty = false
}
// 依賴收集贸营,收集運(yùn)行訪問該計(jì)算屬性的 activeEffect
track(computed, "get" /* GET */, 'value')
return value
},
set value(newValue) {
// 計(jì)算屬性的 setter
setter(newValue)
}
}
return computed
}
- 主要做了三件事情
- 1、標(biāo)準(zhǔn)化參數(shù)
-
computed
函數(shù)接受兩種類型的參數(shù)- 一個(gè)是
getter
函數(shù) - 一個(gè)是擁有
getter
和setter
函數(shù)的對象 - 通過判斷參數(shù)的類型岩睁,我們初始化了函數(shù)內(nèi)部定義的
getter
和setter
函數(shù)
- 一個(gè)是
-
- 2钞脂、創(chuàng)建副作用函數(shù)
runner
-
computed
內(nèi)部通過effect
創(chuàng)建了一個(gè)副作用函數(shù), - 它是對
getter
函數(shù)做的一層封裝 - 創(chuàng)建時(shí)第二個(gè)參數(shù)為
effect
函數(shù)的配置對象-
lazy
為true
: 表示effect
函數(shù)返回的runner
并不會(huì)立即執(zhí)行 -
computed
為true
:表示這是一個(gè)computed effect
捕儒,用于trigger
階段的優(yōu)先級排序 -
scheduler
:表示它的調(diào)度運(yùn)行的方式
-
-
- 3冰啃、創(chuàng)建
computed
對象并返回- 擁有
getter
和setter
函數(shù) - 當(dāng)
computed
對象被訪問的時(shí)候- 首先會(huì)觸發(fā)
getter
- 然后會(huì)判斷是否
dirty
- 如果是就執(zhí)行
runner
,然后做依賴收集
- 首先會(huì)觸發(fā)
- 當(dāng)直接設(shè)置
computed
對象時(shí)- 會(huì)觸發(fā)
setter
肋层,即執(zhí)行computed
函數(shù)內(nèi)部定義的setter
函數(shù)
- 會(huì)觸發(fā)
- 擁有
計(jì)算屬性的運(yùn)行機(jī)制
- 注意
-
computed
內(nèi)部兩個(gè)重要的變量 - 第一個(gè)
dirty
表示一個(gè)計(jì)算屬性的值是否是“臟的”亿笤,用來判斷需不需要重新計(jì)算 - 第二個(gè)
value
表示計(jì)算屬性每次計(jì)算后的結(jié)果
-
- 1、當(dāng)渲染階段訪問到計(jì)算屬性栋猖,則觸發(fā)了計(jì)算屬性的
getter
函數(shù)
get value() {
// 計(jì)算屬性的 getter
if (dirty) {
// 只有數(shù)據(jù)為臟的時(shí)候才會(huì)重新計(jì)算
value = runner()
dirty = false
}
// 依賴收集净薛,收集運(yùn)行訪問該計(jì)算屬性的 activeEffect
track(computed, "get" /* GET */, 'value')
return value
}
- 由于默認(rèn)
dirty
是true
,所以這個(gè)時(shí)候會(huì)執(zhí)行runner
函數(shù)蒲拉,并進(jìn)一步執(zhí)行computed getter
- 如果訪問到了響應(yīng)式對象肃拜,所以就會(huì)觸發(fā)響應(yīng)式對象的依賴收集過程
- 由于是在
runner
執(zhí)行的時(shí)候訪問到了響應(yīng)式對象,所以這個(gè)時(shí)候的activeEffect
是runner
函數(shù) -
runner
函數(shù)執(zhí)行完畢雌团,會(huì)把dirty
設(shè)置為false
- 然后執(zhí)行
track(computed,"get",'value')
函數(shù)做依賴收集 - 這個(gè)時(shí)候
runner
已經(jīng)執(zhí)行完了燃领,所以activeEffect
是組件副作用渲染函數(shù) - 注意:這是兩個(gè)依賴收集過程
- 對于計(jì)算屬性來說,它收集的依賴是組件副作用渲染函數(shù)
- 對于計(jì)算屬性中訪問的響應(yīng)式對象來說锦援,它收集的依賴是計(jì)算屬性內(nèi)部的
runner
函數(shù)
- 當(dāng)修改計(jì)算屬性時(shí)猛蔽,會(huì)派發(fā)通知,此時(shí)這里不是直接調(diào)用
runner
函數(shù)灵寺,而是把runner
作為參數(shù)去執(zhí)行scheduler
函數(shù) -
trigger
函數(shù)內(nèi)部對于effect
函數(shù)的執(zhí)行方式如下:
const run = (effect) => {
// 調(diào)度執(zhí)行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
else {
// 直接運(yùn)行
effect()
}
}
-
computed API
內(nèi)部創(chuàng)建副作用函數(shù)時(shí)曼库,已經(jīng)配置了scheduler
函數(shù)
scheduler: () => {
if (!dirty) {
dirty = true
// 派發(fā)通知,通知運(yùn)行訪問該計(jì)算屬性的 activeEffect
trigger(computed, "set" /* SET */, 'value')
}
}
- 并沒有對計(jì)算屬性求新值略板,而僅僅是把
dirty
設(shè)置為true
- 再執(zhí)行
trigger(computed, "set" , 'value')
毁枯,去通知執(zhí)行計(jì)算屬性依賴的組件渲染副作用函數(shù),即觸發(fā)組件的重新渲染 - 在組件重新渲染的時(shí)候叮称,會(huì)再次訪問 計(jì)算屬性种玛,我們發(fā)現(xiàn)這個(gè)時(shí)候
dirty
為true
藐鹤,然后會(huì)再次執(zhí)行computed getter
image.png
Computed 計(jì)算屬性兩個(gè)特點(diǎn)
- 1、延時(shí)計(jì)算
- 只有當(dāng)我們訪問計(jì)算屬性的時(shí)候赂韵,它才會(huì)真正運(yùn)行
computed getter
函數(shù)計(jì)算
- 只有當(dāng)我們訪問計(jì)算屬性的時(shí)候赂韵,它才會(huì)真正運(yùn)行
- 2娱节、緩存
- 它的內(nèi)部會(huì)緩存上次的計(jì)算結(jié)果
value
,而且只有dirty
為true
時(shí)才會(huì)重新計(jì)算 - 如果訪問計(jì)算屬性時(shí)
dirty
為false
右锨,那么直接返回這個(gè)value
- 它的內(nèi)部會(huì)緩存上次的計(jì)算結(jié)果
Computed 的優(yōu)勢
只要依賴不變化括堤,就可以使用緩存的
value
而不用每次在渲染組件的時(shí)候都執(zhí)行函數(shù)去計(jì)算
嵌套計(jì)算屬性
計(jì)算屬性中訪問另外一個(gè)計(jì)算屬性
const count = ref(0)
const result1 = computed(() => {
return count.value + 1
})
const result2 = computed(() => {
return result1.value + 1
})
console.log(result2.value)
計(jì)算屬性的執(zhí)行順序
- 計(jì)算屬性創(chuàng)建
effect
時(shí)碌秸,標(biāo)記了computed
標(biāo)識的绍移,用于trigger
階段的優(yōu)先級排序 -
trigger
函數(shù)執(zhí)行effects
的過程
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || !shouldTrack) {
if (effect.options.computed) {
computedRunners.add(effect)
}
else {
effects.add(effect)
}
}
})
}
}
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
else {
effect()
}
}
computedRunners.forEach(run)
effects.forEach(run)
- 在添加待運(yùn)行的
effects
的時(shí)候,會(huì)判斷每一個(gè)effect
是不是一個(gè)computed effect
- 如果是的話會(huì)添加到
computedRunners
中 - 在后面運(yùn)行的時(shí)候會(huì)優(yōu)先執(zhí)行
computedRunners
- 然后再執(zhí)行普通的
effects
為什么computed runner
執(zhí)行優(yōu)先于普通的effect
函數(shù)讥电?
- 因?yàn)楫?dāng)修改響應(yīng)式數(shù)據(jù)時(shí)蹂窖,會(huì)觸發(fā)關(guān)聯(lián)的計(jì)算屬性的
runner
和effect
執(zhí)行 - 如果先調(diào)用普通
effect
,這時(shí)dirty
為false
恩敌,使用的數(shù)據(jù)仍然是上次的緩存數(shù)據(jù)瞬测,導(dǎo)致更新不及時(shí)