Axios 封裝實戰:打造高效能且易維護的 API 請求層
在開發中大型專案時,如果每次發送 API 請求都要重複設定 timeout、headers 或手動處理 401 錯誤,不僅程式碼冗餘,管理起來更是噩夢。為了提高 程式碼維護性 與 擴充性,對 Axios 進行二次封裝是前端開發者的必修課。
若還不熟悉基本語法,建議先看 Axios 使用。
為什麼需要封裝 Axios?
傳統的寫法會將邏輯與配置混在一起,如下所示:
axios({
method: 'get',
url: 'https://jsonplaceholder.typicode.com/users',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
Authorization: 'xxx',
},
})
.then((res) => {
console.log(res.data);
})
.catch((err) => {
const status = err?.response?.status;
if (status === 401) console.log('權限過期');
if (status === 403) console.log('無此權限');
console.log(err);
});這段程式碼的問題不是不能跑,而是 每支 API 都要複製一次。網址換了要改幾十個地方,新增一個錯誤碼提示也要逐檔補上。
小提醒
透過封裝,我們可以將 統一配置、錯誤處理 與 環境變數 抽離,實現更優雅的程式碼結構。
如何為 Axios 進行封裝
我們必須統一設定,例如:請求標頭、狀態碼、超過請求的時間、不同環境設置不同接口,並將請求的方法再次封裝與設置請求與回應的攔截器。
Axios 實體化 (Create Instance)
利用 axios.create 建立實體,可以針對不同的 API 來源(例如:測試環境 vs 正式環境)設置不同的基礎路徑。
baseURL:建議搭配環境變數,例如:process.env.VITE_API_URL來切換環境。timeout:統一設置請求超時時間,避免無限等待。headers:設置預設的請求標頭。如果有特殊標頭以參數的方式傳入,將會覆蓋預設的請求標頭。
const instance = axios.create({
baseURL:
import.meta.env.VITE_API_URL || 'https://jsonplaceholder.typicode.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
},
});為什麼不直接改 axios.defaults?
axios.defaults 是 全域設定,一旦專案要同時打自家 API 與第三方服務,兩邊的 baseURL 與 Token 就會互相干擾。axios.create 產生的是獨立實體,各自帶著自己的設定與攔截器,互不影響。環境變數的設定方式可參考 環境變數。
Axios 方法封裝 (API Methods)
將常用的 GET、POST 等方法再次封裝,自訂回傳格式,例如:直接回傳 res.data,能讓呼叫端的程式碼更乾淨。
const api = {
async GET(endPoint, config = {}) {
try {
const res = await instance.get(endPoint, config);
return res.data;
} catch (err) {
return Promise.reject(err);
}
},
async POST(endPoint, data, config = {}) {
try {
const res = await instance.post(endPoint, data, config);
return res.data;
} catch (err) {
return Promise.reject(err);
}
},
};封裝後呼叫端只剩一行,而且拿到的直接就是資料本體:
const users = await api.GET('/users', { params: { _limit: 10 } });Axios 攔截器 (Interceptors) 的核心應用
攔截器是封裝的靈魂,它能在 請求送出前 與 回應收到後 進行加工。
請求攔截 (Request Interceptor)
主要用於 自動注入 Token。不需要在每次發送 API 時手動傳入 Authorization。
回應攔截 (Response Interceptor)
主要用於 統一錯誤處理,例如:401 自動登出、500 系統錯誤提示。
function interceptors() {
instance.interceptors.request.use(
(req) => req,
(err) => {}
);
instance.interceptors.response.use(
(res) => res,
(err) => {}
);
}
interceptors();再做一點補強,如下:
function interceptors() {
instance.interceptors.request.use(
(req) => {
const token = localStorage.getItem('token');
if (token) req.headers.Authorization = `Bearer ${token}`;
return req;
},
(err) => Promise.reject(err)
);
instance.interceptors.response.use(
(res) => res,
(err) => {
switch (err?.response?.status) {
case 401:
console.log('權限過期');
break;
case 403:
console.log('沒有權限');
break;
case 500:
console.log('伺服器錯誤');
break;
default:
console.log('網路有問題');
break;
}
return Promise.reject(err);
}
);
}
interceptors();參考來源:Axios
攔截器的兩個必寫細節
- 一定要 return:請求攔截器要回傳
req,回應攔截器要回傳res,忘記寫請求就會停在這裡不再往下走。 - 錯誤要往外丟:處理完共同邏輯後必須
return Promise.reject(err),否則呼叫端的catch不會觸發,畫面會誤以為請求成功、loading 也關不掉。
401 之後該做什麼?
只印出 權限過期 沒有實際幫助,通常會接著清掉憑證並導回登入頁。搭配 Vue Router 時可以這樣寫:
import router from '@/router';
instance.interceptors.response.use(
(res) => res,
(err) => {
if (err?.response?.status === 401) {
localStorage.removeItem('token');
router.push({ name: 'login' });
}
return Promise.reject(err);
}
);若專案有 導航守衛,兩者會形成完整的防線:守衛負責 進頁面前 檢查,攔截器負責 API 回應後 補救。
Token 放哪裡?
localStorage 方便但會被同源的 JavaScript 讀取,遇到 XSS 時可能外洩。安全需求較高的專案會改用 HttpOnly Cookie,或縮短 Token 有效期並搭配更新機制。
Axios 封裝的分層職責
透過這樣的結構,當 API URL 變更或需要新增全域錯誤提示時,您只需要修改一個地方,就能影響整個專案,這就是封裝的核心價值!
| 模組檔案 | 主要職責 |
|---|---|
| instance.js | 負責 axios.create 基礎設定與環境變數對應。 |
| api.js | 封裝成更易讀的 GET / POST 方法,簡化回傳資料格式。 |
| interceptors.js | 集中處理請求前的 Token 注入與回應後的狀態碼判斷。 |
上面的三個檔案怎麼實際拆開、放在哪個目錄,請看 Axios 模組化。
常見問題
為什麼要用 axios.create 而不是直接改 axios.defaults?
axios.defaults 會影響全域,專案同時要打多個網域或有第三方 API 時容易互相干擾。axios.create 產生獨立實體,每組來源可以有自己的 baseURL、timeout 與攔截器。
攔截器裡一定要 return 嗎?
一定要。請求攔截器必須回傳 config,回應攔截器必須回傳 response,否則請求會停在攔截器不再往下走。錯誤處理則要回傳 Promise.reject(err),才不會把錯誤吞掉。
為什麼錯誤處理完還要 Promise.reject?
因為攔截器只負責共同處理,例如:顯示提示或登出。若不往外丟,呼叫端的 catch 不會被觸發,畫面會以為請求成功,載入狀態也關不掉。
baseURL 要怎麼切換測試與正式環境?
把網址寫進環境變數,例如:Vite 的 import.meta.env.VITE_API_URL,再由 build 指令決定載入哪一份 env 檔,程式碼就不需要為了換環境而修改。
Token 存在 localStorage 安全嗎?
localStorage 會被同源的 JavaScript 讀取,遭遇 XSS 時 Token 可能外洩。安全性要求較高的專案通常改用 HttpOnly Cookie,或縮短 Token 有效期並搭配更新機制。
封裝與模組化差在哪裡?
封裝是把重複的設定與錯誤處理集中起來,模組化 則是進一步把實體、方法與攔截器拆成獨立檔案。先封裝解決重複,再模組化解決檔案過長與職責混雜。