「我有一個 API:GET /api/users/1 回傳 { name: 'Joseph', email: '...' }。怎麼在 React 裡顯示這個資料?」
這個問題聽起來很簡單,但實際上藏著好幾個坑——loading 中要顯示什麼? 請求失敗怎麼辦?userId 變了要不要重新請求?這篇從最基本的方法開始, 逐步演化到更好的寫法,每一步都說清楚「為什麼要這樣改」。
最基本的 fetch + useEffect
先看最直覺的版本。你有一個 userId,想要拿這個 user 的資料。 React 裡「有副作用的事(呼叫 API、操作 DOM)」都要放進 useEffect—— 這是 React 的規定,不是建議。
fetch 的最大陷阱
很多人以為 fetch 拿到 404 或 500 會自動進 .catch(),其實不會。fetch 只有在「網路斷線」時才丟出 Error。 只要收到 Response(不管是 200 還是 500),它都視為「成功」。 所以你必須手動檢查 res.ok。
這個版本是可以運作的,但有一個問題:當 userId 從 1 改成 2 時, 畫面會先顯示 user 1 的資料(舊的 state),等新資料回來才更新。 加上 setIsLoading(true) 在最前面可以解決這個問題—— 切換 userId 時立刻重置。
Skeleton Loading——Loading Spinner 的進化版
Loading spinner(轉圈圈)有個問題:使用者不知道等一下會出現什麼, 心裡沒有預期,感覺等更久。Skeleton 的原理是「先畫出內容的骨架」, 讓使用者知道「等一下大概會在這裡出現一個標題、這裡出現兩行文字」—— 心理上感覺更快,即使實際等待時間一樣。
Facebook、LinkedIn、YouTube 都用 Skeleton,不是沒有原因的。
Skeleton 設計技巧
- Skeleton 的形狀要盡量接近真實內容的形狀
- 文字行高
h-4或h-5,標題h-7或h-8 - 寬度不要都一樣長,錯落排列更像真實文字
- 顏色用
bg-gray-200,不要太深也不要太淺
把 fetch 邏輯抽成 Custom Hook
如果你有很多頁面都需要 fetch 資料——使用者資料、文章列表、留言—— 每個都寫一遍 isLoading、isError、setData,很快就累了,而且改一個地方要改很多次。
Custom Hook 的精髓就是:把重複的邏輯裝進一個函數,讓別人只要給我 URL,我就給你 data + loading + error。這個版本還加了 AbortController—— 當組件卸載時(例如使用者切走這頁),取消還在進行中的請求,避免記憶體洩漏。
有了這個 hook,使用端變得非常簡潔。原本一個元件要寫 20 行 fetch 邏輯, 現在只要一行:
AbortController 為什麼重要?
想像使用者快速切換 Tab,每次切換都觸發一個新的 fetch 請求。 沒有 AbortController 的話,舊的請求還在跑,等它回來時可能已經跑到別的頁面了—— 這時 setState 在已卸載的組件上執行, React 會顯示 warning,嚴重時還會造成 UI 資料錯亂(顯示到其他 tab 的資料)。
為什麼 SWR 比手動 useEffect 更好
自己寫的 useFetch 有個盲點: 每次元件重新 mount,就會重新請求一次 API,就算資料根本沒變。 SWR(stale-while-revalidate)是 Vercel 出的 library,解決的正是這個問題。
自動快取
同一個 URL 在同一個 session 裡不重複請求。A 元件問過 /api/users/1,B 元件問同一個 URL 直接拿快取。
自動重新驗證
切去別的瀏覽器 Tab 再切回來,SWR 會悄悄在背景重新驗證資料有沒有過期——不需要 F5。
自動重試
網路一時斷線請求失敗?SWR 會自動等一下重試,不需要你手動處理 retry 邏輯。
Next.js Server Component — 最現代的方式
前面說的 useEffect、useFetch、SWR 都是「在瀏覽器裡 fetch」—— 代表你的 API URL 會被使用者看到、頁面初次載入時有 loading 畫面、 沒有 JavaScript 就沒有資料。
Next.js 的 Server Component 把這件事翻轉過來:fetch 發生在伺服器上,完成後才把 HTML 送到瀏覽器。使用者看到的第一個畫面就已經有資料了,不需要 loading。
Server Component 的限制
Server Component 在伺服器上執行,所以它沒有 useState、useEffect、onClick 等瀏覽器能力。 如果你的頁面有互動(按鈕、表單、滾動事件),那些部分要另外拆成 Client Component(加上 'use client'), 讓 Server Component 負責「拿資料」,Client Component 負責「互動」。
這就是 Next.js App Router 的核心設計:大部分頁面可以是 Server Component(快、SEO 好),只有需要互動的小塊才 Client Component。
四種方法怎麼選?
不同方法適合不同場景,沒有哪一個是萬能的。看這個表格快速定位:
| 方法 | 適合場景 | 缺點 |
|---|---|---|
| fetch + useEffect | 快速原型、學習理解原理 | 切換 userId 可能有 race condition,沒有快取 |
| useFetch custom hook | 中小型專案、想保持輕量 | 沒有跨組件快取,每次 mount 重新請求 |
| SWR | Client-side 資料,需要自動快取/重驗 | 多一個依賴,需要理解 SWR 的快取模型 |
| Server Component | SEO 重要、初始資料、敏感 API | 無法互動,互動部分要另外 Client Component |
實際建議:Next.js 專案優先用 Server Component 處理初始資料載入; 需要即時更新、使用者互動觸發的 fetch 用 SWR;如果不想多一個套件,自己寫 useFetch 也夠用。 純學習或做 side project,直接 fetch + useEffect 最快上手。
總結
最基本:fetch + useEffect + 三個 state(data、isLoading、error)——理解原理必學。
Skeleton 比 Loading Spinner 有更好的使用者體驗,用 animate-pulse 簡單實現。
Custom hook(useFetch)把重複邏輯封裝起來,記得加 AbortController 防止記憶體洩漏。
SWR 提供自動快取、重新驗證、重試,Client-side 資料的最佳選擇。
Next.js Server Component 在伺服器上 fetch,不需要 loading 狀態,SEO 友好,是最現代的方式。