iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
自我挑戰組

程式碼 AI 寫,我負責看懂:30 天拆解 React × TypeScript系列 第 13

Day 13|Vue 的 watch 到 React 的 useEffect

  • 分享至 

  • xImage
  •  

畫面更新之外,還要執行其他動作

目前 Todo List 中的大部分畫面,都可以直接根據 State 顯示。例如完成數量可以從 todos 計算出來:

const completedCount = todos.filter(
  (todo) => todo.completed,
).length

每當 todos 更新,Component 就會重新 Render,completedCount 也會跟著重新計算。

但有些事情並不是單純產生 JSX。例如想在待辦事項數量改變時,同步修改瀏覽器分頁的標題,就需要在 Render 之外執行其他操作。
這種需要讓 React Component 與瀏覽器 API、計時器或其他外部系統保持同步的情況,可以使用 useEffect()

第一次使用 useEffect()

假設要將目前的待辦事項數量顯示在瀏覽器分頁標題上,可以這樣寫:

import { useEffect } from 'react'

useEffect(() => {
  document.title = `待辦事項:${todos.length}`
}, [todos.length])

useEffect() 會接收一個函式,函式裡放的是 Render 完成後要執行的內容。

後面的:

[todos.length]

稱為依賴陣列,表示這段 Effect 使用了 todos.length。當它改變並完成 Render 後,React 會再次執行 Effect,讓瀏覽器標題與目前的 Todo 數量保持一致。

可以先把整段理解成:

當待辦事項數量改變並完成 Render 後,更新瀏覽器分頁的標題。

依賴陣列會影響執行時機

useEffect() 的寫法不同,執行時機也會有所差異。
沒有提供依賴陣列時,Effect 會在每次 Render 完成後執行:

useEffect(() => {
  console.log('Render 完成了')
})

使用空陣列時,Effect 不會因為 Component 裡的 Props 或 State 改變而再次執行:

useEffect(() => {
  console.log('Component 顯示了')
}, [])

如果依賴陣列中放入資料,Effect 則會在第一次 Render 完成後執行,並在該資料改變後再次執行:

useEffect(() => {
  document.title = `待辦事項:${todos.length}`
}, [todos.length])

目前可以先簡單記成:

  • 沒有依賴陣列:每次 Render 後執行
  • 空陣列 []:不會因 Props 或 State 更新而再次執行
  • 放入依賴資料:該資料改變後再次執行

在開發模式並啟用 Strict Mode 時,React 可能會額外執行一次 Effect 的設定與清除流程。這是 React 用來協助檢查 Effect 是否能正確清除的機制,並不是程式碼失效或執行錯誤。

小小補充:第二個點點的部分(這個 -> 空陣列 [] )一開始我思來想去一直搞不太懂事什麼意思,整個腦袋霧煞煞,後面還是忍不住跑去問AI到底差在哪、到底什麼意思??
最後算是有一點點頭緒(吧)
所以這部分要再簡單一點說的話,我個人目前是覺得應該也可以這麼想:

依賴陣列的差別,主要在於有沒有把資料列入 Effect「再次執行的條件」。

這樣不知道有沒有比較好懂些??
如果有路過看到這段的前輩有更好的解釋/說法,非常非常歡迎留言跟我分享您的想法!!

哪些情況適合使用 useEffect()?

useEffect() 通常用於需要和 React 外部功能同步的情況,例如:

  • 修改瀏覽器分頁標題
  • 設定或清除計時器
  • 建立與中斷網路連線
  • 使用不是由 React 管理的第三方套件
  • 監聽或取消監聽瀏覽器事件

這些操作不是單純根據資料產生畫面,因此比較適合放在 Effect 中處理。

不是所有資料變化都需要 Effect

看到 useEffect() 可以在資料改變後執行程式,很容易開始把各種計算都放進去,但有些資料其實可以直接在 Render 時取得。
目前專案的完成數量就是一個例子:

const completedCount = todos.filter(
  (todo) => todo.completed,
).length

completedCount 完全可以從 todos 計算出來,因此不需要額外建立 State,也不需要使用 useEffect() 監看 todos 後再更新它。
如果資料只是從現有的 Props 或 State 計算而來,通常直接計算會更容易理解。Effect 的重點不是看到資料改變就執行程式,而是讓 Component 與外部系統保持同步。
React 官方文件:你可能不需要 Effect

Effect 也可能需要清除

有些 Effect 會建立持續運作的功能,例如計時器、事件監聽或網路連線。當 Component 不再需要這些功能時,就要將它們清除。

useEffect() 裡的函式可以回傳另一個函式,React 會在適當的時機執行它:

useEffect(() => {
  const timer = setInterval(() => {
    console.log('計時器執行中')
  }, 1000)

  return () => {
    clearInterval(timer)
  }
}, [])

回傳的函式稱為 Cleanup Function。當 Component 從畫面上移除時,React 會執行它;如果依賴資料改變,React 也會先清除前一次 Effect,再執行新的 Effect。
因此,建立了計時器、事件監聽或連線後,也要記得處理相對應的清除工作。

從 watch() 的經驗理解 useEffect()

在 Vue 中,watch() 會監看指定的響應式資料,並在資料變化後執行 Callback。這樣的使用經驗確實可以幫助理解 useEffect(),因為兩者都可能在資料改變後執行某些操作。
不過,React 官方更強調 useEffect() 是用來讓 Component 與外部系統同步。它不是專門用來監看某個變數,也不代表所有資料改變後要做的事情都應該放進 Effect。

兩者在思考方式上的差異可以先整理成:

  • Vue 的 watch():指定要監看的響應式資料,並在變化後執行程式
  • React 的 useEffect():描述一段需要和外部系統同步的操作,依賴陣列則列出這段操作使用的響應式資料

從 Vue 轉到 React 時,可以利用原本使用 watch() 的經驗幫助理解執行時機,但選擇是否使用 useEffect() 時,還是要先確認目前是不是需要與外部系統同步。
React 官方文件:useEffect

今日回顧

  • useEffect() 可以在 React 完成 Render 後執行
  • 依賴陣列決定 Effect 會因為哪些資料改變而再次執行
  • useEffect() 適合用來讓 Component 與瀏覽器 API、計時器或其他外部系統同步
  • 能直接從 Props 或 State 計算出來的資料,通常不需要使用 Effect
  • Effect 建立持續運作的功能時,可能需要回傳 Cleanup Function
  • watch() 的使用經驗可以幫助理解 useEffect(),但兩者的設計方向並不完全相同

依賴陣列雖然看起來像是在指定要監看的資料,但應該是更偏向讓 Effect 使用的資料與外部系統保持同步。

React 在這邊先告一段落,下一篇將進入 TypeScript 的世界探索~下篇見!


上一篇
Day 12|React 事件處理跟 Vue 之間的不同
下一篇
Day 14|認識新朋友,進入 TypeScript 的世界!
系列文
程式碼 AI 寫,我負責看懂:30 天拆解 React × TypeScript22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言