點燈坊

失くすものさえない今が強くなるチャンスよ

使用 watchEffect() 讓代碼更精簡

Sam Xiao's Avatar 2024-02-01

watch() 可追蹤特定 State 處理 Side Effect, 若需求是一開始要先執行 Side Effect,或同時追蹤多個 State,則可改用 Vue 3 新的 watchEffect()

Version

Vue 3.4

watch()

<template>
  <div>Todo ID:<input v-model="todoId" /></div>
  <div>
    <div>ID: {{ todo.id }}</div>
    <div>Title: {{ todo.title }}</div>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

let todoId = ref(1)
let todo = ref({})

watch(
  todoId,
  async () => {
    try {
      let res = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
      todo.value = await res.json()
    } catch(e) {
      console.err(e)
    }
  },
  { immediate: true }
)
</script>

Line 2

<div>Todo ID:<input v-model="todoId" /></div>
<div>
  <div>ID: {{ todo.id }}</div>
  <div>Title: {{ todo.title }}</div>
</div>
  • 當 user 輸入 todoId 後,下方會自動顯示該筆 todo

Line 12

let todoId = ref(1)
let todo = ref(null)
  • todoId state:儲存 user 輸入的 todoId
  • todo state:儲存 API 查詢所回傳的 todo

Line 15

watch(
  todoId,
  async () => {
    try {
      let res = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
      todo.value = await res.json()
    } catch (e) {
      console.err(e)
    }
  },
  { immediate: true }
)
  • watch()
    • todoId:追蹤 count state,當 count state 一有變動,就會執行傳入 function
    • async () => {}:傳入 function
    • { immediate: true }:Eager Watch,會立刻先執行傳入 function,而非等 count state 變動

{ immediate: true } 適合一開始以 預設值 傳入 API 取得資料

watchEffect()

Async Await

<template>
  <div>Todo ID:<input v-model="todoId" /></div>
  <div>
    <div>ID: {{ todo.id }}</div>
    <div>Title: {{ todo.title }}</div>
  </div>
</template>

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

let todoId = ref(1)
let todo = ref({})

watchEffect(async () => {
  try {
    let res = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
    todo.value = await res.json()
  } catch (e) {
    console.error(e)
  }
})
</script>

Line 15

watchEffect(async () => {
  try {
    let res = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
    todo.value = await res.json()
  } catch (e) {
    console.error(e)
  }
})
  • watchEffect()
    • 只需傳入 function 即可,當 function 內所使用的 state 被改變,function 會重新執行一次
    • 該 function 一開始會執行一次,相當於 Eager Watch,也就是 watch() 傳入 { immediate: true }

官網有一句話很有意思:
watchEffect() 仅会在其 同步 执行期间,才追踪依赖。在使用异步回调时,只有在第一个 await 正常工作前访问到的属性才会被追踪

但為什麼會這樣呢?官網沒有做進一步的解釋。

Promise Chain

<template>
  <div>Todo ID:<input v-model="todoId" /></div>
  <div>
    <div>ID: {{ todo.id }}</div>
    <div>Title: {{ todo.title }}</div>
  </div>
</template>

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

let todoId = ref(1)
let todo = ref({})

watchEffect(() => {
  fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
    .then((res) => res.json())
    .then((data) => (todo.value = data))
    .catch((e) => console.error(e))
})
</script>

Line 15

watchEffect(() => {
  fetch(`https://jsonplaceholder.typicode.com/todos/${todoId.value}`)
    .then((res) => res.json())
    .then((data) => (todo.value = data))
    .catch((e) => console.error(e))
})
  • then() 的部分為 非同步
  • todoId state 為 同步,所以會被 watchEffect() 追蹤
  • todo state 為 非同步,所以不會被 watchEffect() 追蹤

非同步todo state 也被 watchEffect() 追蹤,則傳入的 function 會被執行 第二次,甚至 無限多次 ,這就喪失了 watchEffect() 設計的本意,因此官網才說第一個 await 前的 state 才會被追蹤,之後的 state 都不被追蹤

Conclusion

  • watchEffect() 為 Vue 3 新提供的 Composition API,在 Options API 沒有對應寫法
  • Async Await 只是 Promise Chain 的 Syntax Sugar,第一個 await 之後的代碼都是 非同步,也就是都在 then() 裡面
  • watchEffect() 只追蹤 function 內 同步 的 state,而不追蹤 非同步 的 state

Reference

Vue, watchEffect()