上篇我們在單一元件內使用 axios 發送 API,但如果專案規模愈來愈大,需要同時管理多個功能的 API,例如顯示單一書目資料、會員登入系統、查詢會員資料、查詢會員收藏紀錄等,而每次都要重複寫同樣的內容像是 import axios、url、return response 等等等,一直複製也是沒完沒了,所以我們乾脆先來個封裝,把這些重複的瑣事包起來,就在一開始包一次就好,之後搭配使用 Vuex 管理資料狀態時也會更加方便。
那麼先來新增一個名為 utilities 資料夾存放共用工具,並在其中建立 API.js 來封裝 api 內容。
axios.create([config])
利用 create 創造一個 axios 實例來進行個人化的基本設置。以本系列的 API 為例,完整 URL 是「 https://bookshelf.goodideas-studio.com/api 」,主要 domain 是「 https://bookshelf.goodideas-studio.com 」,其後會再接續各種巢狀路由,有時候還會再加上版次,因此可以根據 API 規格設定變數加以彈性控制,組合而成的 baseURL 則是固定發送 API 的基本 URL。
import axios from "axios";
const domain = "https://bookshelf.goodideas-studio.com";
// 版次
// const apiVersion = 'v1';
const bookAPI = axios.create({
baseURL: `${domain}/api`,
// 加上板次
// baseURL: `${domain}/api/${apiVersion}`,
headers: {
"Content-Type": "application/json",
accept: "application/json",
},
});
axios 攔截器可以在發送 requests 或回傳 responses 而進入 then 或 catch 之前,設定需要提前處理的事項。例如會員登入系統需要驗證會員身份,同時登入 API 在設定規格時也會指定請求需在 header 帶上 Token(加密權杖),所以當會員點擊登入按鈕、觸發登入 API 時,趕在發送 requests 到後端之前,先判斷該名會員是否有 Token 資料,若有則將 Token 帶入 header 跟著 request 一起傳給後端,這樣一來後端就能從 request header 取得 Token 進行會員身份的驗證。
// Add a request interceptor
bookAPI.interceptors.request.use(
function (config) {
// Do something before request is sent
// 會員系統需驗證身份時
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
// Add a response interceptor
bookAPI.interceptors.response.use(
function (response) {
// 任何 HTTP status code 為 2xx 開頭時觸發此函式
return response;
},
function (error) {
// 任何 HTTP status code 非 2xx 開頭時觸發此函式
return Promise.reject(error.response);
});
axios 回傳的會是一個 promise 物件,下列圖示為發送 request 後所得到的回傳結果:
config
:發送請求時的配置data
:實際的響應主體,即回傳的資料內容headers
:伺服器發回標頭request
:XMLHttpRequest 物件status
:HTTP 狀態碼statusText
:以文字訊息形式返回的 HTTP 狀態原始 async await 寫法(參考 axios 範例):
async function fetchBooks() {
try {
const response = await axios.get('https://bookshelf.goodideas-studio.com/api');
return response;
} catch (error) {
return Promise.reject(error);
}
}
由於 data
物件內的資料才是我們所需要的資料本身,因此在封裝時直接回傳 promise 物件裡的 data
以取得 API 資料。
// utilities/API.js
async function GET(url, params) {
try {
const response = await bookAPI.get(url, params);
return response.data;
}
catch (error) {
return Promise.reject(error);
}
}
// 封裝其他請求方法如 POST、PUT...
export { GET, POST, PUT };
之後在 Vuex 統一管理 API 發送的流程,由於 baseURL 已將 domain 設為基本設置,因此在 url 只需傳入 API 末段路徑即可。
本系列使用的 API 規格並無末段路徑,因此在此不用帶任何參數直接執行。
// store/index.js
import { GET } from '@/utilities/API';
export default new Vuex.Store({
// ...
actions: {
async fetchBookList(context) {
const books = await GET();
console.log(books); // 所有書單資料
context.commit('bookList',books);
},
}
})
假設今天是要從所有書單裡查詢單一書籍資料,則 API 規格可能會是「 https://bookshelf.goodideas-studio.com/api/books/ISBNId 」,此時就可以使用封裝時的參數進行設置。
actions: {
async fetchBook(context, ISBNId) {
const book = await GET(`/books/${ISBNId}`);
console.log(book); // 單一書籍資料
},
}