Skip to content

Axios 模組化實戰:從封裝進化到專業的檔案架構

Axios 模組化:instance 與 interceptors 拆檔

前一章 我們學會了如何封裝 Axios,但將所有邏輯塞在同一個檔案並不利於 團隊協作單元測試模組化 (Modularization) 的核心價值在於 職責分離 ,讓開發者能更快速地定位問題並擴充功能。

為什麼需要模組化?

模組化不僅是為了好看,更是為了實現以下目標:

  • 單一職責原則 (SRP):每個檔案只負責一件事(建立實體、處理邏輯或攔截請求)。
  • 提高複用性:元件不再依賴特定的 Axios 配置,而是直接呼叫工具模組。
  • 目錄結構化:讓新進成員能一眼看出專案的 API 邏輯置於何處。

推薦的專案目錄結構

在現代前端開發中,我們通常將與框架無關、可複用的工具函數置於 utils 目錄。Axios 的模組化建議採用以下路徑:

src/
 ├─ utils/                    # 通用工具函式庫
 │   └─ axios/                # Axios 模組化核心
 │       ├─ instance.js       # 負責環境設定與實體建立
 │       ├─ api.js            # 負責通用方法封裝 (GET/POST...)
 │       └─ interceptors.js   # 負責 Token 與錯誤處理攔截
 ├─ App.vue
 └─ main.js                   # 進入點,負責註冊攔截器

模組化檔案詳解

實體設定 (instance.js)

專注於 在哪裡請求 以及 請求的預設規則

js
import axios from 'axios';

const instance = axios.create({
  baseURL:
    import.meta.env.VITE_API_URL || 'https://jsonplaceholder.typicode.com',
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json',
  },
});

export default instance;

baseURL 交給 環境變數 決定,測試機與正式機就不必改任何程式碼。

通用方法封裝 (api.js)

將 Axios 原生的方法轉化為更符合專案需求、更乾淨的呼叫介面。

js
import instance from './instance';

export default {
  async GET(endPoint, config = {}) {
    try {
      const res = await instance.get(endPoint, config);
      return res.data;
    } catch (err) {
      console.log(err);
    }
  },
};

錯誤請往外丟,不要只印出來

上面的 console.log(err) 只適合開發時觀察。這樣寫呼叫端會拿到 undefined,卻不知道請求失敗,畫面也就無法顯示錯誤訊息。實務上建議統一改成:

js
} catch (err) {
  return Promise.reject(err);
}

要顯示什麼、要不要重試,交給呼叫端的 try...catch 決定。

邏輯攔截器 (interceptors.js)

將複雜的認證邏輯與錯誤狀態碼處理抽離成獨立函式。

js
import instance from './instance';

export default () => {
  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);
    }
  );
};

在 main.js 全域註冊

js
import { createApp } from 'vue';
import App from './App.vue';
// 匯入攔截器初始化函式
import setupInterceptors from '@/utils/axios/interceptors';

// 執行註冊
setupInterceptors();
createApp(App).mount('#app');

為什麼要在這裡註冊?

建立應用程式 的進入點只會執行一次,能保證攔截器 註冊一次且早於任何請求。如果寫在元件裡,元件每次掛載都會再註冊一組,同一個錯誤就會跳出好幾次提示。

再進一步:依業務拆分 API 模組

當請求數量變多,api.js 只提供 GET / POST 這種 通用方法 已經不夠,建議在上面再疊一層 業務層

src/
 ├─ utils/axios/          # 通用層:實體、方法、攔截器
 └─ api/                  # 業務層:依功能分類
     ├─ user.js           # 登入、取得個人資料
     ├─ product.js        # 商品列表、商品明細
     └─ order.js          # 訂單建立與查詢
js
// api/user.js
import api from '@/utils/axios/api';

export const login = (payload) => api.POST('/auth/login', payload);
export const getProfile = () => api.GET('/user/profile');

元件端就只剩業務語意,完全看不到網址與 Token:

vue
<script setup>
import { ref, onMounted } from "vue";
import { getProfile } from "@/api/user";

const profile = ref(null);

onMounted(async () => {
  try {
    profile.value = await getProfile();
  } catch (err) {
    console.log("取得個人資料失敗");
  }
});
</script>

若這些請求邏輯需要在多個元件共用,還可以再包成 Composable;共用的資料狀態則適合交給 Pinia 管理。

延伸:utils 目錄的其他必備工具

除了 Axios,一個成熟的專案通常會在 utils 目錄下放置以下功能,讓開發更有效率:

  • 格式化:日期、貨幣、數字格式化工具。
  • Storage:封裝 localStoragesessionStorage 的讀取與過期處理。
  • 驗證工具:表單欄位、Email、手機號碼的正則表達式驗證。
  • 效率相關:常用的 debouncethrottle 函式。
  • 權限判斷
  • 常數管理

透過這種 高內聚、低耦合 的模組化管理,您的專案將具備更高的健壯性,也能輕鬆應對日益成長的業務需求!

常見問題

攔截器為什麼要在 main.js 註冊?

為了確保整個應用程式啟動時就完成註冊,而且只註冊一次。若寫在元件裡,元件每次掛載都會重複註冊,同一個錯誤會出現多次提示。

api.js 裡的錯誤該印出來還是往外丟?

建議往外丟。只用 console.log 會讓呼叫端拿到 undefined 卻不知道失敗,畫面無法顯示錯誤狀態。統一 return Promise.reject(err),由呼叫端決定要怎麼呈現。

API 檔案要依業務拆分嗎?

專案有多個功能模組時建議拆。把 userproductorder 各自成檔,函式名稱直接對應業務語意,元件只需匯入需要的那一個檔案。

為什麼放在 utils 而不是 api 目錄?

兩種都常見。utils 強調它是與框架無關的通用工具,api 則強調業務語意。實務上可以把 instanceinterceptorsutils,業務請求函式另外放 api 目錄。

模組化之後元件要怎麼呼叫?

元件只匯入業務層的函式並直接 await,不需要知道 baseURL、Token 或錯誤碼處理。這樣元件內只剩畫面邏輯,測試時也容易替換掉請求層。

延伸閱讀