Vue中 computed 和 watch 的區(qū)別

computedwatchVue 項目中可以說是非常常見,這兩個方法看似都能實現(xiàn)對數(shù)據(jù)的監(jiān)聽,那么兩者之間有什么區(qū)別呢?

computed 計算屬性


計算屬性基于 data 中聲明過或者父組件傳遞的 props 中的數(shù)據(jù)通過計算得到的一個新值,這個新值只會根據(jù)已知值的變化而變化。通俗來講就是:這個屬性依賴其他屬性,由其他屬性計算而來。 得出使用 computed 的常用場景為:

如果一個屬性是由其他屬性計算而來的,這個屬性依賴其他屬性,是一個多對一或者一對一,一般用 computed。

先來看一看 computed 最常用的場景栗子:

<p>姓名:{{ fullName }}</p>
<script>
export default {
  data() {
    return {
      firstName: '張',
      lastName: '三'
    }
  },
  computed: {
    fullName() {
      return this.firstName + this.lastName
    }
  }
};
</script>

computed 屬性對象中定義計算屬性的方法,和取 data 對象里的數(shù)據(jù)屬性一樣以屬性訪問的形式調(diào)用,即在頁面中使用 {{ 方法名 }} 來顯示計算的結(jié)果。

如上栗子中我們在 computed 中使用了 fullName 方法,如果此時我們在 data 中也聲明一個屬性 fullName,會報錯 Duplicated key 'fullName'。因為 data 中的 fullName 屬性和 computed 中的 fullName 方法存在重復。如下栗子:

// 錯誤用法
data() {
  return {
    firstName: '張',
    lastName: '三',
    fullName: '張三'
  }
},
computed: {
  fullName() {
    return this.firstName + this.lastName
  }
}

Vue 的官方文檔中,還特意強調(diào)了 computed 的一個重要特點,就是它具有緩存功能,我們先來看個栗子:

<p>姓名:{{ fullName }}</p>
<p>姓名:{{ fullName }}</p>
<p>姓名:{{ fullName }}</p>
<p>姓名:{{ fullName }}</p>
<p>姓名:{{ fullName }}</p>
<script>
export default {
  data() {
    return {
      firstName: '張',
      lastName: '三'
    }
  },
  computed: {
    fullName() {
      console.log('computed') // 控制臺只打印了一次
      return this.firstName + this.lastName
    }
  }
};
</script>

我們在頁面上多次顯示 fullName ,實際上這個方法只執(zhí)行了一次,所以此處又可得出結(jié)論:computed 中的內(nèi)部方法在重復的調(diào)用中,只要依賴數(shù)據(jù)不變,直接取緩存中的計算結(jié)果。只有依賴型數(shù)據(jù)發(fā)生改變,computed 才會重新計算。

computed 中,所有的屬性都有一個 get() 和一個 set(),默認一般走 get() ,但是如果我們直接修改 fullName 時,則會調(diào)用 set() 方法。我們將上述代碼使用 get()set() 進行拆分,得到如下栗子:

<p>姓名:{{ fullName }}</p>
<button @click="handleClick">更改姓名</button>
<script>
export default {
  data() {
    return {
      firstName: '張',
      lastName: '三'
    }
  },
  computed: {
    fullName: {
      get() {
        return this.firstName + this.lastName
      },
      set(val) {
        console.log(val) // 李四
      }
    }
  }
  methods: {
    handleClick() {
      this.fullName = '李四'
    }
  }
};
</script>

同時 computed 中不支持異步,當computed內(nèi)有異步操作時會報錯,這里用 setTimeout 演示栗子如下:

computed: {
  fullName() {
    setTimeout(() => { // 報錯
      return this.firstName + this.lastName
    }, 30)
  }
}

所以綜上總結(jié) computed 的一些特點如下:

  • 支持緩存,只有依賴數(shù)據(jù)發(fā)生改變,才會重新進行計算
  • 不支持異步,當 computed 內(nèi)有異步操作時無效
  • 如果一個屬性是由其他屬性計算而來的,這個屬性依賴其他屬性,是一個多對一或者一對一,一般用 computed
  • 如果 computed 屬性屬性值是函數(shù),那么默認會走 get() ;函數(shù)的返回值就是屬性的屬性值;在 computed 中的,屬性都有一個 get() 和一個 set(),當數(shù)據(jù)變化時,調(diào)用 set()。

watch 監(jiān)聽屬性


通過 vm 對象的 $watch()watch 配置來監(jiān)聽 Vue 實例上的屬性變化,或某些特定數(shù)據(jù)的變化,然后執(zhí)行某些具體的業(yè)務邏輯和操作。當屬性變化時,回調(diào)函數(shù)自動調(diào)用,在函數(shù)內(nèi)部進行計算。其可以監(jiān)聽的數(shù)據(jù)來源:data,props,computed 內(nèi)的數(shù)據(jù)。

監(jiān)聽函數(shù)有兩個參數(shù),第一個參數(shù)是最新的值,第二個參數(shù)是輸入之前的值,順序一定是先新值再舊值,如果只寫一個參數(shù),那就是最新屬性值。

在使用時選擇 watch 還是 computed,還有一個參考點就是官網(wǎng)說的:當需要在數(shù)據(jù)變化時執(zhí)行異步或開銷較大的操作時,watch方式是最有用的。所以 watch 一定是支持異步的。

watch 默認的是淺監(jiān)聽,(淺監(jiān)聽指只能監(jiān)聽到基礎類型的值變化情況),無法進行深度監(jiān)聽(深度監(jiān)聽指比如引用類型的值變化就無法正確監(jiān)聽)。直接看個栗子吧:

<div>
  <input type="text" v-model="name" />
  <input type="text" v-model="info.city" />
  <button @click="handleClickName">更改姓名</button>
  <button @click="handleClickInfo">更改信息</button>
</div>
<script>
export default {
  data() {
    return {
      name: '哪吒',
      info: {
        city: '北京'
      }
    }
  },
  watch: {
    name(newVal, oldVal) {
      // 值類型,可正常拿到
      console.log('watch name', newVal, oldVal) // 姜子牙,哪吒
    },
    info(newVal, oldVal) {
       // 引用類型,拿不到
      console.log('watch info', newVal, oldVal) // 控制臺無輸出結(jié)果
    },
    info: {
      handler(newVal, oldVal) {
        console.log('watch info', newVal, oldVal)
      },
      deep: true // 深度監(jiān)聽
    }
  },
  methods: {
    handleClickName() {
      this.name = '姜子牙'
    },
    handleClickInfo() {
      this.info.city = '上海'
    }
  }
}
</script>

官方告訴我們使用 watch 監(jiān)聽復雜數(shù)據(jù)類型就需要用到深度監(jiān)聽 deep。

  • deep:為了發(fā)現(xiàn)對象內(nèi)部值的變化,可以在選項參數(shù)中指定 deep: true。注意監(jiān)聽數(shù)組的變更不需要這么做。

我們針對上述代碼使用 deep 進行重寫:

watch: {
  info: {
    handler(newVal, oldVal) {
      console.log('watch info', newVal, oldVal) // {__ob__: Observer}
    },
    deep: true // 深度監(jiān)聽
  }
}

得到打印結(jié)果如下:

watch 深度監(jiān)聽.png

好吧!并沒有得到我們的預期,打印出來的 newValoldVal 值是一樣的,所以深度監(jiān)聽雖然可以監(jiān)聽到對象的變化,但是無法監(jiān)聽到對象里面哪個具體屬性的變化。這是因為它們的引用指向同一個對象/數(shù)組。Vue 不會保留變更之前值的副本。 vm.$watch 深度監(jiān)聽

若果要監(jiān)聽對象的單個屬性的變化,有兩種方法:

  • 直接監(jiān)聽對象的屬性
watch: {
  'info.city'(newVal, oldVal) {
    console.log('watch info', newVal, oldVal) // watch info 上海 北京
  }
}
  • computed 屬性配合使用,computed 返回想要監(jiān)聽的屬性值,watch 用來監(jiān)聽
computed: {
  changeCity() {
    return this.info.city
  }
},
watch: {
  changeCity(newVal, oldVal) {
    console.log('watch info', newVal, oldVal) // watch info 上海 北京
  }
}

上面的代碼都是我們定義了一個按鈕,通過按鈕手動修改值來觸發(fā)值的變化進而使用 watch 進行監(jiān)聽,那么我們能不能在頁面初始化的時候就直接使用 watch 呢?認識 handler 方法和 immediate 屬性。

<p>姓名:{{ fullName }}</p> // 張三
<script>
  data() {
    return {
      firstName: '張',
      lastName: '三',
      fullName: ''
    }
  },
  watch: {
    fullName: {
      handler() {
        this.fullName = this.firstName + this.lastName
      },
      immediate: true // 設置為 true,理解執(zhí)行 handle 方法
    }
  }
</script>

computed 中我們知道每一個屬性其實都有 get()set(),在 watch 中每一個屬性其實都有 handle()imediate 屬性(默認為 false)。watch 的特點是最初綁定的時候是不會執(zhí)行的,而 immediate:true 代表如果在 wacth 里聲明了,那么就會立即先去執(zhí)行里面的 handler 方法。并且如果我們在 computed 中如果定義了 fullName 那么在 data 里就不能重復定義 fullName,而 watch 則不受影響。

日常開發(fā)中,我們還可以使用 watch 來監(jiān)聽路由的變化,如下栗子:

watch: {
  '$route'(to,from){
    console.log(to);   //to表示去往的界面
    console.log(from); //from表示來自于哪個界面
    if(to.path=="/shop/detail"){
      console.log("商品詳情");
    }
  }
},

總結(jié):

watchcomputed 都是以 Vue 的依賴追蹤機制為基礎的,當某一個依賴型數(shù)據(jù)發(fā)生變化的時候,所有依賴這個數(shù)據(jù)的相關(guān)數(shù)據(jù)會自動發(fā)生變化,即自動調(diào)用相關(guān)的函數(shù),來實現(xiàn)數(shù)據(jù)的變動。當依賴的值變化時,在 watch 中,是可以做一些復雜的操作的,而 computed 中的依賴,僅僅是一個值依賴于另一個值,是值上的依賴。

兩者常用應用場景區(qū)分:

computed:用于處理復雜的邏輯運算;一個數(shù)據(jù)受一個或多個數(shù)據(jù)影響;用來處理 watchmethods 無法處理的,或處理起來不方便的情況。例如處理模板中的復雜表達式、購物車里面的商品數(shù)量和總金額之間的變化關(guān)系等。

watch:用來處理當一個屬性發(fā)生變化時,需要執(zhí)行某些具體的業(yè)務邏輯操作,或要在數(shù)據(jù)變化時執(zhí)行異步或開銷較大的操作;一個數(shù)據(jù)改變影響多個數(shù)據(jù)。例如用來監(jiān)控路由、inpurt 輸入框值的特殊處理等。

兩者主要的區(qū)別:


computed
  • 初始化顯示或者相關(guān)的 data、props 等屬性數(shù)據(jù)發(fā)生變化的時候調(diào)用
  • computed 不支持異步;
  • 計算屬性不在 data 中,它是基于 dataprops 中的數(shù)據(jù)通過計算得到的一個新值,這個新值根據(jù)已知值的變化而變化;
  • computed 屬性對象中定義計算屬性的方法,和取 data 對象里的數(shù)據(jù)屬性一樣,以屬性訪問的形式調(diào)用,但是 computed 中定義的屬性名稱不能和 data 中的屬性名稱重合;
  • 如果 computed 屬性值是函數(shù),那么默認會走 get 方法,必須要有一個返回值,函數(shù)的返回值就是屬性的屬性值;
  • computed 屬性值默認會緩存計算結(jié)果,在重復的調(diào)用中,只要依賴數(shù)據(jù)不變,直接取緩存中的計算結(jié)果,只有依賴型數(shù)據(jù)發(fā)生改變,computed 才會重新計算;
  • computed 中的,屬性都有一個 get 和一個 set 方法,當數(shù)據(jù)變化時,調(diào)用 set 方法。
watch
  • 主要用來監(jiān)聽某些特定數(shù)據(jù)的變化,從而進行某些具體的業(yè)務邏輯操作,可以看作是 computedmethods 的結(jié)合體;
  • 可以監(jiān)聽的數(shù)據(jù)來源:datapropscomputed 內(nèi)的數(shù)據(jù);
  • watch 支持異步;
  • 不支持緩存,監(jiān)聽的數(shù)據(jù)改變,直接會觸發(fā)相應的操作;
  • 監(jiān)聽函數(shù)有兩個參數(shù),第一個參數(shù)是最新的值,第二個參數(shù)是輸入之前的值,順序一定是新值,舊值。

如果文中有不對的地方或者理解有誤的地方歡迎大家提出并指正。每一天都要相對前一天進步一點,加油?。?!

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

友情鏈接更多精彩內(nèi)容