# 大綱
- Vite 設定 proxy 方法
- axios 攔截器如何設定 URL
- 實際使用 axios 的 API
# Vite 設定 proxy 方法
在 Vite 中,我們可以通過在 vite.config.js
文件中設定一下,就可以做好應用程式的 proxy,讓 Vite 透過 proxy 呼叫 API
Vite 預設已經有個 defineConfig 方法
直接在在裡面寫一個 server 物件 ,設定好相關參數就完成啦
export default defineConfig({ | |
host: "0.0.0.0", // 這一行是為了讓其他裝置可以連到本機的開發環境,沒這需求可以忽略 | |
server: { | |
proxy: { | |
"/api": { | |
target: "https://www.test.com/", // 後端 API 進入點 | |
changeOrigin: true, | |
secure: false, | |
ws: true, | |
rewrite: (path) => path.replace(/^\/api/, ""), | |
}, | |
}, | |
}, | |
}); |
設定過程中採到最大的雷,是那個 /api
字樣,其實是自己定義的任意參數!!
這只會在開發過程中使用到,這點等等說明到 axios 攔截器設定時,看 code 去比對就會比較清楚
# 設定 Axios 實體 (Axios Instance)
剛開始學習使用 axios 的時候,相信大家都是 cdn 或是 npm 設定好 axios 後
就直接 axios.get()
、 axios.post()
開始使用。在小型專案這麼用,並沒有什麼太大的問題
但如果今天開發的專案量大,需要呼叫的 API 一多,相同的邏輯很有機會要寫好幾次,如果需求一更動,就容易牽一髮動全身
而 Axios Instance 就是一個解決這個問題很好用的工具
所以…
# 為什麼要用 Axios Instance
接續前言,Axios Instance 可以提供以下功能讓管理 API 更方便
- 集中設定 Request Config
- 支援攔截器,可在 then/catch 前做額外處理
- 封裝 API 易於管理
廢話不多說,上 code
// 首先,先 import npm 安裝好後的 axios | |
import axios from "axios"; |
一般來說,如果沒有搭配本篇文章主題 Vite proxy整合 axios攔截器
實體的宣告會長這樣,直接將 API 的 絕對路徑
寫在 baseURL
參數中
const instance = axios.create({ | |
baseURL: "https://www.test.com/", | |
headers: { "Content-Type": "application/json" }, | |
timeout: 50000, | |
}); |
但因為我們已經在 vite 設定好 proxy
所以這段 code 可以改寫成這樣
const service = axios.create({ | |
baseURL: import.meta.env.DEV ? "/api" : "https://www.test.com/", | |
headers: { "Content-Type": "application/json" }, | |
timeout: 50000, | |
}); |
其中 import.meta.env.DEV
是 vite 所提供用來判斷開發模式的寫法
DEV
是用來判斷是否運行在開發環境,其餘的內容可以參考這裡的說明
另一個關注點是 開發環境寫了 "/api"
,非開發環境則是寫 "https://www.test.com/"
先前有提到 "https://www.test.com/"
是實際的運行的 API 進入點
而 "/api"
則是開頭在 proxy 設定的 "/api": {}
參數,由於有寫一段 rewrite: (path) => path.replace(/^\/api/, "")
,所以在開發環境呼叫 API 時,所有 API url 中,包含 /api
的字樣都會被取代掉
# Axios 攔截器
設定好實體之後,就可以透過下列的 code 建立 request
與 respones
第一個函數 instance.interceptors.request
會運行在後續呼叫 API 時的初始
也就是所有 request 資料在拋給後端之前,會先在這裡被擋下來
可以在這裡做一些資料送出前預設處理,例如設定 api 需不需要設定 token?需要的話在 header 增加參數之類
第二個函數 instance.interceptors.request
方便之處,在於可以統一管理系統發生錯誤時
預設希望處理的事情,像是跳出警訊、重新導向頁面等
instance.interceptors.request.use( | |
(config) => { | |
// Do something before request is sent | |
return config; | |
}, | |
(error) => { | |
// Do something before request is sent | |
return Promise.reject(error); | |
} | |
); | |
instance.interceptors.response.use( | |
(response) => { | |
// Do something with response data | |
return response; | |
}, | |
(error) => { | |
if (error.response) { | |
switch (error.response.status) { | |
case 400: | |
console.log("something wrong"); | |
break; | |
case 404: | |
console.log("你要找的頁面不存在"); | |
// go to 404 page | |
break; | |
case 500: | |
console.log("程式發生問題"); | |
// go to 500 page | |
break; | |
default: | |
console.log(error.message); | |
} | |
} | |
if (!window.navigator.onLine) { | |
alert("網路出了點問題,請重新連線後重整網頁"); | |
return; | |
} | |
return Promise.reject(error); | |
} | |
); | |
export default instance; |
# 封裝 api
上述都設置好後,就可以這樣使用 api
import request from "@/plugins/axios"; | |
export function Login(data) { | |
return request({ | |
url: "/login", | |
method: "post", | |
data, | |
}); | |
} |
在.vue 檔案需要呼叫 api 時,就可以這麼寫
import { Login } from "@/models/api"; | |
const loginHandler = async () => { | |
const res = await login({ | |
identifier: identifier.value, | |
password: password.value, | |
}); | |
console.log("login", res); | |
}; |
值得注意的是我們的開發網址雖然是
http://localhost:5173/
而且 axios baseURL 在開發環境設定了/api
但是因為設定 vite proxy,實際上運行了這個login()
呼叫的完整網址會是https://www.test.com/login
而不會是http://localhost:5173/api/login
打完收工~~
# 補充
補充一下如果需要在 resquest 時判斷 API 要不要加 token
可以這樣寫
import { getToken, setToken } from "@/utils/auth"; | |
instance.interceptors.request.use( | |
(config) => { | |
if (config.needToken) { | |
const token = getToken(); | |
config.headers.Authorization = `Bearer ${token}`; | |
} | |
return config; | |
}, | |
(error) => { | |
// Do something before request is sent | |
return Promise.reject(error); | |
} | |
); |
而 api.js 中如果有 api 需要 token 的話,就可以這樣設定
export function updateProfile(data) { | |
return request({ | |
url: "/me/profile", | |
method: "post", | |
data, | |
needToken: true, | |
}); | |
} |
而這是在 plugins/axios.js
有用到 getToken
與 setToken
方法的實際內容
import Cookies from "js-cookie"; // 要 npm 安裝 js-cookie | |
const TOKEN_KEY = "asscess_token"; // 設定 cookie 中要存 token 的 key | |
const EXPIRES_AT = "expires_at"; // 設定 token 存在 cookie 的效期,一般是跟後端約定好過期時效 | |
// 設定 token | |
export const setToken = (token, expires_at) => { | |
Cookies.set(TOKEN_KEY, token); | |
Cookies.set(EXPIRES_AT, expires_at); | |
}; | |
export const getToken = () => Cookies.get(TOKEN_KEY); // 取得 token | |
export const getExpiresAt = () => Cookies.get(EXPIRES_AT); // 取得時效 | |
// 確認目前瀏覽器的 token 過期沒 | |
export const isTokenExpired = () => { | |
const accessToken = getToken(); | |
const expiresAt = getExpiresAt(); | |
if (!accessToken || !expiresAt) { | |
return true; | |
} | |
const now = new Date().getTime(); | |
return now > Number(expiresAt); | |
}; |