在過去幾天的修煉中,我們已經建立起一套完整的登入與狀態管理機制。在上一回,我們甚至在 authStore
中定義了一個名為 fetchUserProfile
的 action,準備在應用程式初始化時去獲取使用者資料。但我們留下了一行code:
// const response = await apiClient.get('/me');
這行程式碼代表了我們前端應用與後端世界溝通的橋樑。今天,我們將修煉「APIの呼吸・壹之型:Backend連接」,學習如何使用 axios
將其封裝成一個易於管理、可重複使用的模組。
雖然瀏覽器內建了 fetch
API,但 axios
提供了更多在大型應用中不可或缺的功能:
baseURL
、timeout
等。因此,axios
長期以來都是 Vue 中進行 HTTP 通訊的首選。
首先,將 axios
加入我們的專案。
npm install axios
接著,一個好的實踐是建立一個專門的檔案來管理 axios
的設定,而不是在每個元件中都 import axios
。讓我們在 src
下建立一個新資料夾 api
,並在其中建立 client.js
。
// src/api/client.js
import axios from 'axios';
// 使用 axios.create() 建立一個新的 axios 實例
const apiClient = axios.create({
// 從環境變數讀取後端 API 的基礎 URL
baseURL: import.meta.env.VITE_API_BASE_URL,
// 設定請求超時時間,單位為毫秒
timeout: 10000,
// 設定通用的請求標頭
headers: {
'Content-Type': 'application/json',
},
});
export default apiClient;
我們還需要在 .env
檔案中加入後端 API 的網址:
# .env
VITE_API_BASE_URL="https://your-backend-api.com/api"
攔截器是 axios
最強大的功能,它允許我們在請求或回應被處理前,先對其進行攔截和修改。
我們不希望在每次需要驗證的 API 請求中,都手動加上 Authorization
標頭。請求攔截器可以完美地自動化這個過程。
修改 src/api/client.js
:
// src/api/client.js
import axios from 'axios';
import { useAuthStore } from '../stores/auth';
// ... apiClient 的建立
// 請求攔截器
apiClient.interceptors.request.use(
(config) => {
const authStore = useAuthStore();
const token = authStore.token;
if (token) {
// 如果 token 存在,則在每個請求的標頭中加入 Authorization
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
// 對請求錯誤做些什麼
return Promise.reject(error);
},
);
export default apiClient;
現在,只要我們是登入狀態,所有透過 apiClient
發出的請求,都會自動帶上 Bearer Token
!
如果 token
過期或無效,後端通常會回傳 401 Unauthorized
狀態碼。我們不希望在每個 API 呼叫中都寫一次處理 401
的邏輯。回應攔截器可以幫我們集中處理。
繼續修改 src/api/client.js
:
// src/api/client.js
// ...
import router from '../router'; // 引入 router
// ...
// 回應攔截器
apiClient.interceptors.response.use(
(response) => {
// 狀態碼為 2xx 的情況
// 直接回傳回應,或只回傳 response.data
return response;
},
(error) => {
// 狀態碼非 2xx 的情況
if (error.response) {
switch (error.response.status) {
// 401: 未授權,通常是 token 無效或過期
case 401:
{
const authStore = useAuthStore();
// 清除本地的使用者狀態
authStore.logout();
// 強制跳轉到登入頁面
router.push({ name: 'login' });
// 可以在這裡顯示一個錯誤提示,例如「登入已過期,請重新登入」
console.error('Unauthorized, logging out.');
}
break;
// 其他錯誤狀態碼,例如 404, 500 等
default:
console.error(`發生錯誤: ${error.response.status}`);
break;
}
}
// 將錯誤繼續拋出,讓呼叫的地方可以單獨處理
return Promise.reject(error);
},
);
export default apiClient;
這個回應攔截器極大地簡化了我們的錯誤處理。任何地方的 API 呼叫,一旦遇到 401
,都會被自動登出並導向登入頁,無需在元件中編寫任何額外程式碼!
最後,我們將 API 請求本身也封裝成模組,讓元件的職責更單純。
建立 src/api/auth.js
:
// src/api/auth.js
import apiClient from './client';
// 獲取當前登入使用者的資料
export const fetchUserProfile = () => {
return apiClient.get('/me');
};
// (範例)驗證來自 Google 的 token
export const verifyGoogleToken = (googleToken) => {
return apiClient.post('/auth/google-verify', { token: googleToken });
};
現在,我們可以回到 src/stores/auth.js
,修改一下原本的程式碼。
// src/stores/auth.js
import { defineStore } from 'pinia';
import { fetchUserProfile } from '../api/auth'; // 引入封裝好的 API 函式
export const useAuthStore = defineStore('auth', {
// ... state, getters
actions: {
// ... login, logout
async fetchUserProfile() {
if (this.token) {
try {
const response = await fetchUserProfile();
this.user = response.data;
} catch (error) {
// 因為攔截器會處理 401,所以這裡主要處理其他可能的錯誤
// 如果 token 無效,攔截器會自動登出,這個 catch 可能不會被執行
console.error('獲取使用者資料失敗', error);
}
}
},
},
});
今天,透過封裝 axios
,我們實現了:
baseURL
和 timeout
等設定集中管理。401
自動登出等強大功能。這套 API Client 的架構,不僅讓我們當前的程式碼更健壯,也為未來新增更多複雜的 API 請求打下了良好的基礎。
明日,Day 23:[APIの呼吸・貳之型] 錯誤處理 - 攔截器與重試機制。心を燃やせ 🔥!