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

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

computed 計(jì)算屬性


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

如果一個(gè)屬性是由其他屬性計(jì)算而來(lái)的,這個(gè)屬性依賴其他屬性,是一個(gè)多對(duì)一或者一對(duì)一,一般用 computed。

先來(lái)看一看 computed 最常用的場(chǎng)景栗子:

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

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

如上栗子中我們?cè)?computed 中使用了 fullName 方法,如果此時(shí)我們?cè)?data 中也聲明一個(gè)屬性 fullName,會(huì)報(bào)錯(cuò) Duplicated key 'fullName'。因?yàn)?data 中的 fullName 屬性和 computed 中的 fullName 方法存在重復(fù)。如下栗子:

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

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

<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') // 控制臺(tái)只打印了一次
      return this.firstName + this.lastName
    }
  }
};
</script>

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

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

<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>

同時(shí) computed 中不支持異步,當(dāng)computed內(nèi)有異步操作時(shí)會(huì)報(bào)錯(cuò),這里用 setTimeout 演示栗子如下:

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

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

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

watch 監(jiān)聽(tīng)屬性


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

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

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

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

<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) // 控制臺(tái)無(wú)輸出結(jié)果
    },
    info: {
      handler(newVal, oldVal) {
        console.log('watch info', newVal, oldVal)
      },
      deep: true // 深度監(jiān)聽(tīng)
    }
  },
  methods: {
    handleClickName() {
      this.name = '姜子牙'
    },
    handleClickInfo() {
      this.info.city = '上海'
    }
  }
}
</script>

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

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

我們針對(duì)上述代碼使用 deep 進(jìn)行重寫:

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

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

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

好吧!并沒(méi)有得到我們的預(yù)期,打印出來(lái)的 newValoldVal 值是一樣的,所以深度監(jiān)聽(tīng)雖然可以監(jiān)聽(tīng)到對(duì)象的變化,但是無(wú)法監(jiān)聽(tīng)到對(duì)象里面哪個(gè)具體屬性的變化。這是因?yàn)樗鼈兊囊弥赶蛲粋€(gè)對(duì)象/數(shù)組。Vue 不會(huì)保留變更之前值的副本。 vm.$watch 深度監(jiān)聽(tīng)

若果要監(jiān)聽(tīng)對(duì)象的單個(gè)屬性的變化,有兩種方法:

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

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

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

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

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

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

總結(jié):

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

兩者常用應(yīng)用場(chǎng)景區(qū)分:

computed:用于處理復(fù)雜的邏輯運(yùn)算;一個(gè)數(shù)據(jù)受一個(gè)或多個(gè)數(shù)據(jù)影響;用來(lái)處理 watchmethods 無(wú)法處理的,或處理起來(lái)不方便的情況。例如處理模板中的復(fù)雜表達(dá)式、購(gòu)物車?yán)锩娴纳唐窋?shù)量和總金額之間的變化關(guān)系等。

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

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


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

如果文中有不對(duì)的地方或者理解有誤的地方歡迎大家提出并指正。每一天都要相對(duì)前一天進(jìn)步一點(diǎn),加油!?。?/p>

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

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