iT邦幫忙

2025 iThome 鐵人賽

DAY 5
0
Vue.js

在 Vue 過氣前要學的三十件事系列 第 5

在 Vue 過氣前要學的第五件事 - 主動還是被動

  • 分享至 

  • xImage
  •  

前言

在上一篇 在 Vue 過氣前要學的第四件事 - 2025 了還要用 .value ?
我們講了 refreactive 之間的差異,這篇就接著說入門會接觸的第二種易混淆 API。

就是 computed & watch ,第一次看到可能會覺得說,
有點像,但又說不出哪裡像。

感覺都是去抓某個值,然後執行下一步操作,

至少我一開始學是有搞混的,還是只有我那麼白癡

但其實只要看了官方文件加上一些 Sample code 就蠻好解釋差異的,
那最後還會補充說明一下 watchwatchEffect 的差異。

使用

computed

先來講計算屬性 computed
我不知道大家看到這個計算屬性是有什麼感覺。

const sum = computed(() => 1 + 1);
// 是這樣嗎?
const hasBook = computed(() => (author.books.length > 0 ? "Yes" : "No"));
// 還是這樣?
const articleTitle = computed(() => article.title);
// 那這樣算嗎?

計算這兩個字對於我來說很難有精準的一個描述。

那我們來看一下這段 :

推薦使用計算屬性來描述依賴響應式狀態的複雜邏輯 - computed #基礎示例

OK,所以我們可以把這個計算屬性看做是工廠的處理器 input 進去,output 出來我們所要的格式
https://ithelp.ithome.com.tw/upload/images/20250904/20172784ZWrMkxXnxE.png

<script setup>
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})
</script>
<template>
    <span>我 {{ author.books.length > 0 ? "有" : "沒有" }} 書</span>
    <!-- 我有書 -->
</template>

假設你今天在做一個線上讀書網站,並且想在<template>表示說,
喔這個作者是否有著作,那你可能會用上面這種寫法。

這樣有個問題,把邏輯寫在 <template> 其實不是那麼方便閱讀,
而且你可能在多個地方都要用的這個值, 當你這樣貼來貼去,無疑是增加了 bug 的風險。

因此這種對值執行邏輯操作,舉凡拼接、計算、判斷等等,都可以用 computed 來省去許多功夫。

小提醒:
如果你是第一次看到這種寫法,
這個叫做 條件 (三元) 運算子,語法是 條件 ? 條件成立返回的值 : 條件不成立返回的值
用來做簡易的邏輯操作是很好用的寫法,但沒運用好會造成可讀性變差。

上面的程式碼也會變成這樣 :

<script setup>
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})
+ const hasBook = computed(() => (author.books.length > 0 ? "有" : "沒有"));
</script>
<template>
-    <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
+    <span>我 {{ hasBook }} 書</span>
     <!-- 我有書 -->
</template>

那我們就實作了一個很簡單的計算屬性。

最佳實踐

  1. 你不該在 computed 內執行副作用
  2. 不該對 computed 重新賦值,因為 computed 返回值只是一個臨時狀態,不代表原值。

副作用 :
對外部環境做的操作可以統稱為副作用,這個外面通常是指不被 {} 包住的區域,
常見的有修改全局變量、打 API、修改 DOM 元素、etc.

watch

接下來輪到偵聽器 watch

如果說剛剛的 computed 是用來做邏輯操作?
watch 不能拿來做邏輯操作嗎?

const hasSnowman = ref(false);
watch(
  () => product.snowman.length,
  () => {
    hasSnowman.value = product.snowman.length > 0 ? "產線開始生產了" : "他們在裝忙";
  },
  { immediate: true }
);
console.log(hasSnowman.value); // 產線開始生產了

其實也可以,但有沒有注意到一件事,我必須更改外部參數,而不是直接使用。
(示範用例子,這樣寫其實不是那麼清楚)

這就是 watch 的核心概念,他會去監聽某個資料,要是有變更,那就會執行後面的動作
https://ithelp.ithome.com.tw/upload/images/20250904/20172784MojuPWVs2C.png

所以到底差在哪?

計算屬性允許我們聲明性地計算衍生值。然而在有些情況下,我們需要在狀態變化時執行一些“副作用”:例如更改 DOM,或是根據異步操作的結果去修改另一處的狀態。 vue-docs

其實這邊官方文件就很好的說明了 computedwatch 的差異。

簡單的統整就是,
computed 是用來對依賴值操作,並返回一個值,
watch 則是觀察某個依賴項變化, 再去執行副作用

一定要用 computed 嗎? 我用 function 也可以對數值操作阿

<script setup>
// 略...
function hasBook() {
  return author.books.length > 0;
}
</script>

<template>
  <p>{{ hasBook() }}</p>
  <!-- true -->
</template>

確實,但 computed 有一個優點,就是會有 cache ( 快取? 緩存?

也就是說只要你的依賴項沒有更改的情況,
你每次呼叫這個 function 都會返回之前計算的結果,也就省去重複計算的效能。

Q: watch 跟 watchEffect 差在哪?


有時候你會遇到 source 跟 callback 都用到同一個值的情況

const todoId = ref(1)
const data = ref(null)

watch(
  todoId, // 這邊監聽了 todoId
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}` // 這邊也用到了 todoId
    )
    data.value = await response.json()
  },
  { immediate: true }
)

等於說你用了兩次 todoId,這時候就可以考慮用 watchEffect,


- watch(
-  todoId, // 這邊監聽了 todoId
-  async () => {
+ watchEffect(async () => {
   const response = await fetch(
     `https://jsonplaceholder.typicode.com/todos/${todoId.value}` // 這邊也用到了 todoId
   )
  data.value = await response.json()
+ })
-  },
-  { immediate: true }
-)

瞬間少一大段,超爽的,上面主要的差異有兩個

  1. 不用傳入 source 參數。
  2. 不用寫 { immediate: true },還會自動追蹤依賴項。

所以誰是主動誰是被動?

Both.
在預設情況下, computedwatch 都是懶執行( lazy )
這個好處是如果依賴項沒有更改,那就會回傳上次執行的值。

不過有個小問題就是,初始化進來其實不會啟動

那該怎麼辦呢? 其實我們上面有寫到,就是使用 { immediate: true }
這會讓 watch 從 lazy 變成 eager,初始化的時候會先啟動一次,然後接續後面的監聽。

Q: 我需要停止監聽嗎? 會不會造成 memory leaks ?

… will be automatically stopped when the owner component is unmounted. In most cases, you don't need to worry about stopping the watcher yourself.

… 並在卸載所有者元件時自動停止。在大多數情況下,您無需擔心自己停止 watcher。

OK 爽。
好啦還是有例外,如果你今天要執行異步操作,像這樣。

<script setup>
import { watchEffect } from 'vue'

// 它會自動停止
watchEffect(() => {})

// ...這個則不會!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>

這種情況就要手動停止, 不過這應該是少數情況

const unwatch = watchEffect(() => {})

// ...當該偵聽器不再需要時
unwatch()

結語

這篇文章原本只是要探討 computedwatch 的差異,
但不知不覺就打好多ㄌ,其實還有一些沒有講到。

一句話解釋何時該用 watch 何時該用 computed
只要沒有需要更改外部環境,請先用 computed

例如用來清理副作用並重新啟動的 onWatcherCleanup
在資料更新當下馬上觸發副作用,優先級更高的 watchSyncEffect

但很多都是有用到再去文件查就好了,對官方文件做筆記梳理差異還是能找到許多沒想過的東西。

一些小練習

  1. computedwatch 在觸發執行的行為上有什麼差異?
    誰是惰性(lazy)運算,誰是主動(eager)監聽?
  2. watchwatchEffect 各自的使用時機。

資料來源

  1. Vue - computed
  2. Vue - watcher
    https://ithelp.ithome.com.tw/upload/images/20250905/20172784sRTsSR5Y9y.png

上一篇
在 Vue 過氣前要學的第四件事 - 2025 了還要用 .value ?
下一篇
在 Vue 過氣前要學的第六件事 - 響應式到底為什麼那麼重要
系列文
在 Vue 過氣前要學的三十件事6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言