computed與watch的區(qū)別

如果一個值依賴多個屬性(多對一),用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ā)條件,在某些時候 methodscomputed 看不出來具體的差別,但是一旦在運算量比較復雜的頁面中,就會體現出不一樣。
注意:computed 是具有緩存的,這就意味著只要計算屬性的依賴沒有進行相應的數據更新,那么 computed 會直接從緩存中獲取值,多次訪問都會返回之前的計算結果。

總結

在 computed 和 watch 方面,一個是計算,一個是觀察,在語義上是有區(qū)別的。
計算是通過變量計算來得出數據,而觀察是觀察一個特定的值,根據被觀察者的變動進行相應的變化,在特定的場景下不能相互混用,所以還是需要注意 api 運用的合理性和語義性。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容