「我知道 useState 了。但我想在頁面載入時呼叫 API 拿資料,該怎麼做? 為什麼不能直接在 Component 裡呼叫 fetch?」
這篇解釋清楚 useEffect——React 最常讓初學者困惑的 Hook。 不從「定義」開始,直接從你遇到的問題開始。
為什麼不能直接 fetch?
很直覺的想法:「我要資料,就直接呼叫 fetch 啊。」來試試看:
為什麼會無限迴圈?
React Component 是一個函式,每次狀態改變都會重新執行這個函式。 如果你在函式裡直接 fetch,就會觸發這個死循環:
你需要一個方式告訴 React:「等你把 UI 渲染完,再來做 fetch 這件事,而且只做一次。」 這就是 useEffect 的設計目的。
副作用(Side Effect)是什麼
「副作用」聽起來嚇人,其實很簡單
Component 的「主業」是根據資料回傳 UI(就是那個 return 裡的 JSX)。 任何不是「回傳 UI」的事情,都叫副作用(Side Effect):
- 呼叫 API、發 HTTP 請求
- 設定計時器(setTimeout、setInterval)
- 訂閱事件(WebSocket、EventEmitter)
- 修改 document.title
- 操作瀏覽器的 localStorage
useEffect 就是說:「等你把 UI 渲染完,再來做這些副業。」
useEffect 的三種用法
這是最重要的部分。useEffect 的行為完全由第二個參數(dependency array)決定:
只在「第一次渲染」執行(最常用)
空陣列 [] 的語義是:「沒有任何 dependency,所以只在最初執行一次,之後不管什麼改變都不重跑。」
當特定資料改變時執行
這個模式超級常用:「當你選了不同的使用者 ID,就重新抓那個使用者的資料」。 useEffect 監聽 userId,它一改變,副作用就重新執行。
每次渲染都執行(幾乎不用)
幾乎不會用到這個形式。一旦 effect 裡有 setState,就會:執行 effect → setState → 重新渲染 → 執行 effect → 無限迴圈。
三種寫法對比
| 寫法 | 執行時機 | 使用場景 |
|---|---|---|
useEffect(() => {}, []) | 只執行一次(初次渲染後) | 初始化資料請求 |
useEffect(() => {}, [x]) | x 改變時執行 | 依賴某個 state / prop |
useEffect(() => {}) | 每次渲染後都執行 | 幾乎不用 |
完整的資料請求模式
真實專案裡,資料請求不只是 fetch 那麼簡單。你還需要處理「載入中」狀態和「錯誤」狀態—— 否則使用者盯著空白頁面等,根本不知道發生什麼事:
初始狀態
isLoading: true使用者進入頁面,還沒拿到資料。顯示 spinner 或骨架屏,告訴使用者「正在載入」。
錯誤狀態
error: stringAPI 掛了、網路斷線、認證失敗。顯示有意義的錯誤訊息,不要讓使用者面對空白。
成功狀態
user: User資料拿回來了,正常渲染。這才是你的「主要 UI」,但它只是三種狀態之一。
這個模式在 React 專案裡無處不在
isLoading + error + data 是最基本的非同步資料模式。 之後你會學到 React Query(TanStack Query),它把這整個模式包裝成一行 const { data, isLoading, error } = useQuery(...), 原理是一樣的,只是幫你把樣板程式碼都處理掉了。
Cleanup——為什麼有些 useEffect 要「收拾」
報紙訂閱的比喻
想像你訂了一份報紙。你搬家了,但沒有通知報社取消訂閱—— 報紙一直送到舊地址,浪費資源,也可能引發奇怪的問題(例如舊鄰居收到你的報紙)。
Cleanup 就是「搬家前先取消訂閱」。 當 Component 從畫面消失時,你要負責清除你啟動的副作用, 不然那些副作用會繼續跑,繼續嘗試更新已經不存在的 Component。
什麼時候需要 Cleanup?
需要 Cleanup
- ✓ setInterval / setTimeout
- ✓ WebSocket 連線
- ✓ EventListener 訂閱
- ✓ 第三方 library 的訂閱
- ✓ 正在進行中的 fetch(可用 AbortController)
不需要 Cleanup
- ✓ 一次性的 fetch 請求
- ✓ 修改 document.title
- ✓ 寫入 localStorage
- ✓ 記錄 log
三個最常見的錯誤
學會用法之後,這三個錯誤幾乎每個初學者都會犯。早點認識它們,少走彎路:
錯誤 1:忘記 Dependency Array → 無限迴圈
如果你的 effect 只需要執行一次,用空陣列 []。 如果依賴某個值,把那個值放進 dependency array。
錯誤 2:Dependency Array 不完整 → 資料過時(Stale Closure)
ESLint 救你一命
安裝 eslint-plugin-react-hooks, 規則 react-hooks/exhaustive-deps 會提醒你 把所有在 effect 裡用到的值都加進 dependency array。Next.js 預設已啟用。
錯誤 3:在 useEffect 直接用 async → 回傳了 Promise
這是 React 文件明確提到的規則:useEffect 的 callback 必須是同步函式。 如果要用 async/await,就在 callback 裡定義一個 async 函式,然後呼叫它。
本篇重點回顧
掌握了 useEffect,你就打通了 React 資料請求的任督二脈。 下一篇進入 State 管理模式——當 useState 不夠用時,你需要什麼?
上一篇
EP.04 — React 核心概念
Component · JSX · Props · State
下一篇
EP.17 — State 管理模式
useState 的邊界 · useReducer · Context