目前 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])
目前可以先簡單記成:
[]:不會因 Props 或 State 更新而再次執行在開發模式並啟用 Strict Mode 時,React 可能會額外執行一次 Effect 的設定與清除流程。這是 React 用來協助檢查 Effect 是否能正確清除的機制,並不是程式碼失效或執行錯誤。
小小補充:第二個點點的部分(這個 -> 空陣列 [] )一開始我思來想去一直搞不太懂事什麼意思,整個腦袋霧煞煞,後面還是忍不住跑去問AI到底差在哪、到底什麼意思??
最後算是有一點點頭緒(吧)
所以這部分要再簡單一點說的話,我個人目前是覺得應該也可以這麼想:
依賴陣列的差別,主要在於有沒有把資料列入 Effect「再次執行的條件」。
這樣不知道有沒有比較好懂些??
如果有路過看到這段的前輩有更好的解釋/說法,非常非常歡迎留言跟我分享您的想法!!
useEffect() 通常用於需要和 React 外部功能同步的情況,例如:
這些操作不是單純根據資料產生畫面,因此比較適合放在 Effect 中處理。
看到 useEffect() 可以在資料改變後執行程式,很容易開始把各種計算都放進去,但有些資料其實可以直接在 Render 時取得。
目前專案的完成數量就是一個例子:
const completedCount = todos.filter(
(todo) => todo.completed,
).length
completedCount 完全可以從 todos 計算出來,因此不需要額外建立 State,也不需要使用 useEffect() 監看 todos 後再更新它。
如果資料只是從現有的 Props 或 State 計算而來,通常直接計算會更容易理解。Effect 的重點不是看到資料改變就執行程式,而是讓 Component 與外部系統保持同步。
React 官方文件:你可能不需要 Effect
有些 Effect 會建立持續運作的功能,例如計時器、事件監聽或網路連線。當 Component 不再需要這些功能時,就要將它們清除。
useEffect() 裡的函式可以回傳另一個函式,React 會在適當的時機執行它:
useEffect(() => {
const timer = setInterval(() => {
console.log('計時器執行中')
}, 1000)
return () => {
clearInterval(timer)
}
}, [])
回傳的函式稱為 Cleanup Function。當 Component 從畫面上移除時,React 會執行它;如果依賴資料改變,React 也會先清除前一次 Effect,再執行新的 Effect。
因此,建立了計時器、事件監聽或連線後,也要記得處理相對應的清除工作。
在 Vue 中,watch() 會監看指定的響應式資料,並在資料變化後執行 Callback。這樣的使用經驗確實可以幫助理解 useEffect(),因為兩者都可能在資料改變後執行某些操作。
不過,React 官方更強調 useEffect() 是用來讓 Component 與外部系統同步。它不是專門用來監看某個變數,也不代表所有資料改變後要做的事情都應該放進 Effect。
兩者在思考方式上的差異可以先整理成:
從 Vue 轉到 React 時,可以利用原本使用 watch() 的經驗幫助理解執行時機,但選擇是否使用 useEffect() 時,還是要先確認目前是不是需要與外部系統同步。
React 官方文件:useEffect
useEffect() 可以在 React 完成 Render 後執行useEffect() 適合用來讓 Component 與瀏覽器 API、計時器或其他外部系統同步watch() 的使用經驗可以幫助理解 useEffect(),但兩者的設計方向並不完全相同依賴陣列雖然看起來像是在指定要監看的資料,但應該是更偏向讓 Effect 使用的資料與外部系統保持同步。
React 在這邊先告一段落,下一篇將進入 TypeScript 的世界探索~下篇見!