如果一個值依賴多個屬性(多對一)袱耽,用computed肯定是更加方便的。 如果一個值變化后會引起一系列操作,或者一個值變化會引起一系列值的變化(一對多)澄峰,用watch更加方便一些。 watch 支持異步代碼而computed 不支持
1.計算屬性 computed
特點:
- 支持緩存辟犀,只有依賴數(shù)據(jù)發(fā)生改變俏竞,才會重新進行計算;
- 不支持異步堂竟,當 computed 內(nèi)有異步操作時無效魂毁,無法監(jiān)聽數(shù)據(jù)的變化;
- computed 屬性值會默認走緩存出嘹,計算屬性是基于它們的響應式依賴進行緩存的席楚。也就是基 于 data 中聲明過或者父組件傳遞的 props 中的數(shù)據(jù)通過計算得到的值;
- 如果一個屬性是由其他屬性計算而來的税稼,這個屬性依賴其他屬性 是一個多對一或者一對一酣胀,一般用computed;
- 如果 computed 屬性值是函數(shù)娶聘,那么默認會走 get 方法闻镶,函數(shù)的返回值就是屬性的屬性值;在computed中的丸升,屬性都有一個get和一個 set 方法铆农,當數(shù)據(jù)變化時,調(diào)用 set 方法;
使用例子:
<template>
<div>
<span>{{testName}}</span>
<el-input v-model="firstText"></el-input>
<el-input v-model="lastText"></el-input>
<el-input v-model="mergeText1"></el-input>
<el-input v-model="mergeText2"></el-input>
<div>{{fullNameFun()}}</div>
</div>
</template>
<script>
import { defineComponent, computed, ref } from 'vue'
export default defineComponent({
setup() {
let firstText = ref('hello')
let lastText = ref('world')
const mergeText1 = computed(() => firstText.value + ' ' + lastText.value)
const mergeText2 = computed({
// getter
get() {
// 回調(diào)函數(shù) 當需要讀取當前屬性值時執(zhí)行墩剖,根據(jù)相關(guān)數(shù)據(jù)計算并返回當前屬性的值
return `${firstText.value} ${lastText.value}`
},
// setter
set(val) {
//監(jiān)視當前屬性值的變化猴凹,當屬性值發(fā)生變化時執(zhí)行,更新相關(guān)的屬性數(shù)據(jù),val就是fullName的最新屬性值
const names = val.split(' ')
console.log(names)
firstText.value = names[0]
lastText.value = names[names.length - 1]
},
})
function fullNameFun(){
return firstText.value+ ' ' + lastText.value
}
return {
firstText,
lastText,
mergeText1,
mergeText2,
fullNameFun
}
}
})
</script>
優(yōu)點:
- 當改變 ref 或者 reactive 響應式變量值時岭皂,整個應用會重新渲染郊霎,vue 會被數(shù)據(jù)重新渲染到 dom 中。這時爷绘,如果我們模板中使用了 methods 中的fullNameFun函數(shù)书劝,或者使用了組合式return的函數(shù)。隨著渲染土至,方法也會被調(diào)用购对。但是 如果computed中所依賴的變量沒有發(fā)生改變,則不會進行重新的計算陶因,從而性能開銷比較小骡苞。當新的值需要大量計算才能得到,緩存的意義就非常大楷扬;
- 如果 computed 所依賴的數(shù)據(jù)發(fā)生改變時解幽,計算屬性才會重新計算,并進行緩存烘苹;當改變其他數(shù)據(jù)時躲株,computed 屬性 并不會重新計算,從而提升性能螟加;
- 當拿到的值需要進行一定處理使用時徘溢,就可以使用 computed;
2. 偵聽屬性 watch (vue3中還有watcheffect詳細講解捆探,在另外一篇文章中傳送門)
特點:
- 完全等同于vue2 中的watch
- 不支持緩存然爆,數(shù)據(jù)變化,直接會觸發(fā)相應的操作黍图;
- watch 支持異步操作曾雕;
- 監(jiān)聽的函數(shù)接收兩個參數(shù),第一個參數(shù)是最新的值助被;第二個參數(shù)是輸入之前的值剖张;
當一個屬性發(fā)生變化時,需要執(zhí)行對應的操作揩环,一對多搔弄;- 監(jiān)聽數(shù)據(jù)必須是 data 中聲明過或者組合式api中聲明的響應式值或者父組件傳遞過來的 props 中的數(shù)據(jù)。當數(shù)據(jù)變化時觸發(fā)其他操作丰滑,函數(shù)有兩個參數(shù):
immediate
:組件加載立即觸發(fā)回調(diào)函數(shù)執(zhí)行顾犹;deep
: 深度監(jiān)聽;為了發(fā)現(xiàn)對象內(nèi)部值的變化,復雜類型的數(shù)據(jù)時使用炫刷,例如:數(shù)組中的對象內(nèi)容的改變擎宝,注意:監(jiān)聽數(shù)組的變動不需要這么做。注意:deep無法監(jiān)聽到數(shù)組的變動和對象的新增浑玛,參考vue數(shù)組變異,只有以響應式的方式觸發(fā)才會被監(jiān)聽到绍申;
注:當需要在數(shù)據(jù)變化時執(zhí)行異步或開銷較大的操作時,這個方式是最有用的顾彰,這是和 computed 最大的區(qū)別极阅。
2.1 一般用法,監(jiān)聽單個變量或多個數(shù)據(jù)源或者一個函數(shù)返回值
注:監(jiān)聽一個函數(shù)的返回值拘央,當函數(shù)里面中所用到的變量發(fā)生變化都會觸發(fā)回調(diào)
// 偵聽一個 getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接偵聽一個 reactive
const count = ref(0)
watch(state , (newValue, oldValue) => {
/* ... */
})
// 直接偵聽一個 ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
// 監(jiān)聽多個數(shù)據(jù)源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
// watch 可以監(jiān)聽一個函數(shù)的返回值
watch(() => {
return otherName.firstName + otherName.lastName
},
value => {
// 當otherName中的 firstName或者lastName發(fā)生變化時涂屁,都會進入這個函數(shù)
console.log(`我叫${value}`)
}
)
2.2 監(jiān)聽復雜數(shù)據(jù)(深度監(jiān)聽 deep)
不使用 deep 時书在,當我們改變 obj.a 的值時灰伟,watch 不能監(jiān)聽到數(shù)據(jù)變化,默認情況下儒旬,watch 只監(jiān)聽屬性引用的變化栏账,也就是只監(jiān)聽了一層,但改對象內(nèi)部的屬性是監(jiān)聽不到的栈源。
immerdiate 屬性: 通過聲明 immediate 選項為 true挡爵,可以立即執(zhí)行一次 watch里面的函數(shù)。
import { watch, ref, reactive } from 'vue'
export default {
setup() {
let obj= reactive({
text:'hello'
})
watch(obj, (newValue, oldValue) => {
// 回調(diào)函數(shù)
}, {
immediate: true,
deep: true
})
return {
obj
}
}
}
通過使用 deep: true 進行深入觀察甚垦,我們監(jiān)聽 obj茶鹃,會把 obj 下面的屬性層層遍歷,都加上監(jiān)聽事件艰亮,這樣做性能開銷也會變大闭翩,只要修改 obj 中任意屬性值,都會觸發(fā)回調(diào)迄埃,那么如何優(yōu)化性能呢疗韵?
可以直接對用對象 . 屬性的方法拿到屬性,也就是上面說道的偵聽一個 getter
import { watch, ref, reactive } from 'vue-router'
export default {
setup() {
let obj= reactive({
text:'hello'
})
watch(()=>obj.text, (newValue, oldValue) => {
// 回調(diào)函數(shù)
}, {
immediate: true,
deep: true
})
return {
obj
}
}
}
注意事項:
- watch 中的函數(shù)名稱必須是所依賴 data 中的屬性名稱;(vue2)
- watch 中的函數(shù)是不需要調(diào)用的侄非,只要函數(shù)所依賴的屬性發(fā)生了改變 那么相對應的函數(shù)就會執(zhí)行蕉汪;
- watch 中的函數(shù)會有2個參數(shù) 一個是新值,一個是舊值逞怨;
- watch 默認情況下無法監(jiān)聽對象的改變者疤,如果需要進行監(jiān)聽則需要進行深度監(jiān)聽 深度監(jiān)聽需要配置 handler
(vue2中才需要)
函數(shù)以及 deep 為true。(因為它只會監(jiān)聽對象的地址是否發(fā)生了改變叠赦,而值是不會監(jiān)聽的)驹马;(vue2)在vue3中有些許區(qū)別。在另外一篇文章中再說
- watch 默認情況下第一次的時候不會去做監(jiān)聽,如果需要在第一次加載的時候也需要去做監(jiān)聽的話需要設置 immediate:true窥翩;
vue2中數(shù)組響應式原理:
1 重新定義原生數(shù)組方法push unshift shift pop splice sort reverse 因為這些方法可以修改原數(shù)組业岁。
2 拿到原生數(shù)組方法 Object.create(Array.prototype)
3 AOP攔截,再執(zhí)行重寫數(shù)組方法前,先執(zhí)行原生數(shù)組方法
watch 在特殊情況下是無法監(jiān)聽到數(shù)組的變化
所以,vue2中對數(shù)組的解決方案:
- 通過下標來更改數(shù)組中的數(shù)據(jù)寇蚊;
- 通過 length 來改變數(shù)組的長度笔时;
通過 Vue 實例方法 set 進行設置 $set( target, propertyName/index, value)
參數(shù): target {Object | Array}
, propertyName/index {string | number}
仗岸, value {any}
this.$set(this.arr,0,100);
通過 splice
來數(shù)組清空 $delete( target, propertyName/index )
參數(shù):target {Object | Array}
允耿, propertyName/index {string | number}
this.$delete(this.arr,0)
vue2中深度監(jiān)聽對應的函數(shù)名必須為 handler ,否則無效果,因為 watche r里面對應的是對 handler 的調(diào)用
劃重點:在vue3中利用的是ES6的proxy,對數(shù)據(jù)響應式進行一個數(shù)據(jù)的代理扒怖,可以監(jiān)控到數(shù)組的變化较锡。vue3中如果想要讓一個對象變?yōu)轫憫綌?shù)據(jù),可以使用
reactive
或ref
盗痒。因此$set
在vue3中廢棄
3. 方法 methods
methods
跟前面的都不一樣蚂蕴,我們通常在這里寫入方法,只要調(diào)用就會重新執(zhí)行一次俯邓,相應的有一些觸發(fā)條件骡楼,在某些時候methods
和computed
看不出來具體的差別,但是一旦在運算量比較復雜的頁面中稽鞭,就會體現(xiàn)出不一樣鸟整。
注意:computed
是具有緩存的,這就意味著只要計算屬性的依賴沒有進行相應的數(shù)據(jù)更新朦蕴,那么computed
會直接從緩存中獲取值篮条,多次訪問都會返回之前的計算結(jié)果。
總結(jié)
在 computed 和 watch 方面吩抓,一個是計算涉茧,一個是觀察,在語義上是有區(qū)別的琴拧。
計算是通過變量計算來得出數(shù)據(jù)降瞳,而觀察是觀察一個特定的值,根據(jù)被觀察者的變動進行相應的變化蚓胸,在特定的場景下不能相互混用挣饥,所以還是需要注意 api 運用的合理性和語義性。