
從 XMLHttpRequest 到 React Query,前端跟 API 打交道的方式進化了好幾代。
先講結論
如果你只想記一個結論:簡單請求用原生 fetch,需要攔截器或統一錯誤處理用 axios,React/Vue 專案有超過三個 API 呼叫就用 React Query 或 SWR。剩下的看完這篇你就知道為什麼了。
XHR:遠古時代的武器
XMLHttpRequest(XHR)是最早的非同步請求方式,2005 年 Gmail 用它實現了不用整頁刷新就能收信的功能,當時驚為天人。但語法真的很繁瑣:
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.onerror = function() { console.error('炸了'); };
xhr.send();現在幾乎不會直接寫 XHR 了,但它有一個 fetch 做不到的事:追蹤上傳進度。所以如果你需要做上傳進度條,XHR 還是有存在的價值。
Fetch API:現代標準
2015 年出來的原生 API,基於 Promise,語法簡潔很多:
// GET
const response = await fetch('/api/users');
const users = await response.json();
// POST
await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'John' })
});但 fetch 有兩個新手必踩的坑:
坑一:HTTP 4xx/5xx 不會 reject。fetch('/api/not-found') 回傳 404,它不會進 catch——你得自己檢查 response.ok。這個設計哲學是「只有網路錯誤才是真的錯誤」,但實務上真的很容易忘記檢查。
坑二:預設不帶 Cookie。跨域請求要加 credentials: 'include',同源也要加 credentials: 'same-origin'。
async function fetchUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}取消請求用 AbortController——在 React 的 useEffect cleanup 裡特別重要,不然使用者快速切換頁面時,可能會更新到已卸載元件的 state。
Axios:fetch 的豪華版
需要裝套件,但功能完整很多:
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
});
// 請求攔截器:自動帶 Token
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 回應攔截器:統一處理 401
api.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
window.location.href = '/login';
}
return Promise.reject(error);
}
);Axios 最大的優勢是攔截器——自動帶 Token、統一錯誤處理、自動 JSON 轉換,這些 fetch 都要自己手工做。
Promise 跟 async/await
這兩個不是「發請求的工具」,而是「處理非同步操作的語法」。不管你用 fetch 還是 axios,底層都靠 Promise。
// Promise 並行(同時發多個請求)
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);async/await 就是 Promise 的語法糖,讓非同步程式碼看起來像同步。但記住一個常見的效能問題:如果兩個請求沒有依賴關係,不要用 await 串行執行,用 Promise.all 並行才是對的。
React Query / SWR:前端資料請求的終極形態
上面的工具都只解決了「怎麼發請求」。但實際的 React/Vue 專案裡,你還得管 loading 狀態、error 狀態、快取、重新請求、分頁…全部手動管理會讓你崩潰。
// React Query — 一個 hook 搞定所有狀態
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 5 * 60 * 1000, // 5 分鐘內不重新請求
});SWR 是 Vercel 出的替代方案,API 更簡單(useSWR(url, fetcher) 就搞定),適合不需要太多進階功能的專案。React Query 功能更完整,有 Devtools、離線支援、完整的 Mutation 管理。
我的建議:app 有超過三個 API 呼叫,就不要自己管 loading state 了。React Query 或 SWR 省下來的時間和少寫的 bug,絕對值得多裝一個套件。
怎麼選?
| 場景 | 推薦 |
|---|---|
| 簡單一次性請求 | fetch |
| 需要攔截器/統一錯誤處理 | axios |
| React/Vue 專案的資料請求 | React Query / SWR |
| 檔案上傳需要進度條 | axios 或 XHR |
| SSR / Server Component | fetch(Next.js 原生支援) |
工具的選擇不難,難的是知道什麼時候該用什麼。好消息是這篇看完你就知道了。
延伸閱讀
MDN - Fetch API Axios 官方文件 TanStack Query 官方文件 SWR 官方文件 RESTFul API API CRUD