如果一個值依賴多個屬性(多對一),用computed肯定是更加方便的。 如果一個值變化后會引起一系列操作,或者一個值變化會引起一系列值的變化(一對多),用watch更加方便一些。 watch 支持異步代碼而computed 不支持
1.計算屬性 computed
特點:
- 支持緩存,只有依賴數據發(fā)生改變,才會重新進行計算;
- 不支持異步,當 computed 內有異步操作時無效,無法監(jiān)聽數據的變化;
- computed 屬性值會默認走緩存,計算屬性是基于它們的響應式依賴進行緩存的。也就是基 于 data 中聲明過或者父組件傳遞的 props 中的數據通過計算得到的值;
- 如果一個屬性是由其他屬性計算而來的,這個屬性依賴其他屬性 是一個多對一或者一對一,一般用computed;
- 如果 computed 屬性值是函數,那么默認會走 get 方法,函數的返回值就是屬性的屬性值;在computed中的,屬性都有一個get和一個 set 方法,當數據變化時,調用 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() {
// 回調函數 當需要讀取當前屬性值時執(zhí)行,根據相關數據計算并返回當前屬性的值
return `${firstText.value} ${lastText.value}`
},
// setter
set(val) {
//監(jiān)視當前屬性值的變化,當屬性值發(fā)生變化時執(zhí)行,更新相關的屬性數據,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 會被數據重新渲染到 dom 中。這時,如果我們模板中使用了 methods 中的fullNameFun函數,或者使用了組合式return的函數。隨著渲染,方法也會被調用。但是 如果computed中所依賴的變量沒有發(fā)生改變,則不會進行重新的計算,從而性能開銷比較小。當新的值需要大量計算才能得到,緩存的意義就非常大;
- 如果 computed 所依賴的數據發(fā)生改變時,計算屬性才會重新計算,并進行緩存;當改變其他數據時,computed 屬性 并不會重新計算,從而提升性能;
- 當拿到的值需要進行一定處理使用時,就可以使用 computed;
2. 偵聽屬性 watch (vue3中還有watcheffect詳細講解,在另外一篇文章中傳送門)
特點:
- 完全等同于vue2 中的watch
- 不支持緩存,數據變化,直接會觸發(fā)相應的操作;
- watch 支持異步操作;
- 監(jiān)聽的函數接收兩個參數,第一個參數是最新的值;第二個參數是輸入之前的值;
當一個屬性發(fā)生變化時,需要執(zhí)行對應的操作,一對多;- 監(jiān)聽數據必須是 data 中聲明過或者組合式api中聲明的響應式值或者父組件傳遞過來的 props 中的數據。當數據變化時觸發(fā)其他操作,函數有兩個參數:
immediate:組件加載立即觸發(fā)回調函數執(zhí)行;deep: 深度監(jiān)聽;為了發(fā)現對象內部值的變化,復雜類型的數據時使用,例如:數組中的對象內容的改變,注意:監(jiān)聽數組的變動不需要這么做。注意:deep無法監(jiān)聽到數組的變動和對象的新增,參考vue數組變異,只有以響應式的方式觸發(fā)才會被監(jiān)聽到;
注:當需要在數據變化時執(zhí)行異步或開銷較大的操作時,這個方式是最有用的,這是和 computed 最大的區(qū)別。
2.1 一般用法,監(jiān)聽單個變量或多個數據源或者一個函數返回值
注:監(jiān)聽一個函數的返回值,當函數里面中所用到的變量發(fā)生變化都會觸發(fā)回調
// 偵聽一個 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)聽多個數據源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
// watch 可以監(jiān)聽一個函數的返回值
watch(() => {
return otherName.firstName + otherName.lastName
},
value => {
// 當otherName中的 firstName或者lastName發(fā)生變化時,都會進入這個函數
console.log(`我叫${value}`)
}
)
2.2 監(jiān)聽復雜數據(深度監(jiān)聽 deep)
不使用 deep 時,當我們改變 obj.a 的值時,watch 不能監(jiān)聽到數據變化,默認情況下,watch 只監(jiān)聽屬性引用的變化,也就是只監(jiān)聽了一層,但改對象內部的屬性是監(jiān)聽不到的。
immerdiate 屬性: 通過聲明 immediate 選項為 true,可以立即執(zhí)行一次 watch里面的函數。
import { watch, ref, reactive } from 'vue'
export default {
setup() {
let obj= reactive({
text:'hello'
})
watch(obj, (newValue, oldValue) => {
// 回調函數
}, {
immediate: true,
deep: true
})
return {
obj
}
}
}
通過使用 deep: true 進行深入觀察,我們監(jiān)聽 obj,會把 obj 下面的屬性層層遍歷,都加上監(jiān)聽事件,這樣做性能開銷也會變大,只要修改 obj 中任意屬性值,都會觸發(fā)回調,那么如何優(yōu)化性能呢?
可以直接對用對象 . 屬性的方法拿到屬性,也就是上面說道的偵聽一個 getter
import { watch, ref, reactive } from 'vue-router'
export default {
setup() {
let obj= reactive({
text:'hello'
})
watch(()=>obj.text, (newValue, oldValue) => {
// 回調函數
}, {
immediate: true,
deep: true
})
return {
obj
}
}
}
注意事項:
- watch 中的函數名稱必須是所依賴 data 中的屬性名稱;(vue2)
- watch 中的函數是不需要調用的,只要函數所依賴的屬性發(fā)生了改變 那么相對應的函數就會執(zhí)行;
- watch 中的函數會有2個參數 一個是新值,一個是舊值;
- watch 默認情況下無法監(jiān)聽對象的改變,如果需要進行監(jiān)聽則需要進行深度監(jiān)聽 深度監(jiān)聽需要配置 handler
(vue2中才需要)函數以及 deep 為true。(因為它只會監(jiān)聽對象的地址是否發(fā)生了改變,而值是不會監(jiān)聽的);(vue2)在vue3中有些許區(qū)別。在另外一篇文章中再說- watch 默認情況下第一次的時候不會去做監(jiān)聽,如果需要在第一次加載的時候也需要去做監(jiān)聽的話需要設置 immediate:true;
vue2中數組響應式原理:
1 重新定義原生數組方法push unshift shift pop splice sort reverse 因為這些方法可以修改原數組。
2 拿到原生數組方法 Object.create(Array.prototype)
3 AOP攔截,再執(zhí)行重寫數組方法前,先執(zhí)行原生數組方法
watch 在特殊情況下是無法監(jiān)聽到數組的變化
所以,vue2中對數組的解決方案:
- 通過下標來更改數組中的數據;
- 通過 length 來改變數組的長度;
通過 Vue 實例方法 set 進行設置 $set( target, propertyName/index, value)
參數: target {Object | Array} , propertyName/index {string | number}, value {any}
this.$set(this.arr,0,100);
通過 splice 來數組清空 $delete( target, propertyName/index )
參數:target {Object | Array} , propertyName/index {string | number}
this.$delete(this.arr,0)
vue2中深度監(jiān)聽對應的函數名必須為 handler ,否則無效果,因為 watche r里面對應的是對 handler 的調用
劃重點:在vue3中利用的是ES6的proxy,對數據響應式進行一個數據的代理,可以監(jiān)控到數組的變化。vue3中如果想要讓一個對象變?yōu)轫憫綌祿梢允褂?code>reactive或
ref。因此$set在vue3中廢棄
3. 方法 methods
methods跟前面的都不一樣,我們通常在這里寫入方法,只要調用就會重新執(zhí)行一次,相應的有一些觸發(fā)條件,在某些時候methods和computed看不出來具體的差別,但是一旦在運算量比較復雜的頁面中,就會體現出不一樣。
注意:computed是具有緩存的,這就意味著只要計算屬性的依賴沒有進行相應的數據更新,那么computed會直接從緩存中獲取值,多次訪問都會返回之前的計算結果。
總結
在 computed 和 watch 方面,一個是計算,一個是觀察,在語義上是有區(qū)別的。
計算是通過變量計算來得出數據,而觀察是觀察一個特定的值,根據被觀察者的變動進行相應的變化,在特定的場景下不能相互混用,所以還是需要注意 api 運用的合理性和語義性。