利用 Axios 來封裝 API

還蠻不錯的想法。

簡述

以前因為我總認為 Axios 是個有點多餘的 library,因為不過就是把 fetch 包裝成 axios.getaxios.post 而已,那幹嘛不直接 fetch 就好了?

但一直到最近才發現原來有 Axios 有提供 instance(實體)的功能來讓你方便管理 API。實際用過以後覺得還不錯,所以來做個紀錄。

前置作業

等一下會提到幾個東西:

  • axios.create() 用來建立 instance
  • instance.interceptors 攔截器,用來處理發出 req 之前和拿到 res 之前要做的事

接著來邊看 code 邊解釋。

首先會先建立一個 axios 的實體:

附註:這邊用的測試 API 是這支

1
2
3
4
5
6
7
const instance = axios.create({
// 之後發 req 時就能寫相對路徑,例如: /users/1
baseURL: "https://reqres.in/api/",
headers: { 'Content-Type': 'application/json' },
// 超過幾 ms 就算失敗
timeout: 20000
})

建立好以後它就跟一般的 axios 一樣,可以用 instance.get instance.post 之類的方法,不過在那之前我們會先設定「攔截器(interceptors)」。

攔截器就是讓你設定:

  • request 發出去以前我想做什麼事?
  • response 拿到以後,把值回傳給其他 handler 以前要做什麼?

所以 request 的部分會這樣設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 第一個 function 會拿到 config
// 第二個 function 會拿到 error(發生錯誤時)
instance.interceptors.request.use(
function (config) {
// 可以在這裡修改 config
// 這邊只是隨便示範,例如在這加上 Auth token header
config.headers.Authorization = 'Bearer 12345';
return config;
},
function (err) {
return Promise.reject(err);
}
)

而 response 的部分通常會著重在錯誤處理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
instance.interceptors.response.use(
// 成功的處理
function (res) {
// 如果想做些事情可以在這裡做
return res;
},
// 失敗的處理
function (err) {
if (err.response) {
switch (err.response.status) {
case 404:
console.log('不存在');
// 導向 404 頁面
break
case 500:
console.log('伺服器錯誤');
break
default:
console.log(err.message);
}
}
if (!window.navigator.onLine) {
alert("你網路有問題哦!")
return
}
return Promise.reject(err)
}
)

封裝

雖然上面做好後就可以直接用了,不過更好的方式是把他做封裝起來,讓我們在用的時候更直覺,像這樣:

1
2
3
4
5
// 取得的第一頁的 user 列表
gerUserList({
page: 1
}).then(res => console.log('Get user list', res.data))
.catch(err => console.log('err', err))

經過包裝後就能很快知道我們要用哪隻 API,跟要帶的資料、參數是什麼。

不然原本的話得這樣寫:

1
2
3
instance.get('/users/1')
.then(res => console.log(res))
.catch(err => console.log('err', err))

所以第一步是先把 method 用 function 來包裝起來,讓它可以透過參數來決定要做什麼事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// parms 是 query string,例如:https://example[?gender=female&age=20]
export function api (method, url, data = null, config) {
method = method.toLowerCase();
switch (method) {
case "post":
return instance.post(url, data, config)
case "get":
return instance.get(url, { params: data })
case "delete":
return instance.delete(url, { params: data })
case "put":
return instance.put(url, data)
case "patch":
return instance.patch(url, data)
default:
console.log('unknown method type');
return false;
}
}

附註-關於 axios 的參數值

我原本不太懂上面的參數值為什麼可以那樣設,所以這邊才留個補充。如果你本來就看得懂的話請無視這一段。

來舉個例子,假設我想發一個 POST 請求,那我有兩種寫法。

第一種是用 post() 方法 + url + data + config

1
2
3
4
5
6
7
8
9
axios.post("https://example.com/api/users", {
name: "PeaNu",
age: 20,
des: "..."
}, {
params: {
token: "123456"
}
})

第二種是全部塞在 config

1
2
3
4
5
6
7
8
9
10
11
12
axios({
method: "post",
url: 'https://aaa.com/api/users',
data: {
name: 'PeaNU',
age: 20,
des: "..."
},
params: {
token: "12345"
}
})

總之這兩個東西會是一樣的,只是寫法不同而已。

而上面用的 instance.post 正是第一種寫法,所以才要傳給他 url + data + config 這三個參數。

最後再補充一件事,就是 get()delete() 因為不需要傳資料,所以只會有 urlconfig 這兩個參數,所以上面才會寫成 instance.delete(url, { params: data }) 的形式。

如果怕忘記的話可以參考 vs code 的 TypeScript 提示

typescript

OK,解說就到這邊為止。

接著新增一個檔案,讓跟剛剛 export 出來的 function 在做一層包裝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// user.js
import { api } from "./api";
const URL = "/users";

export const gerUserList = (data) => {
return api("GET", URL, data);
}

export const addUser = (data) => {
return api("POST", URL, data);
}

export const putUser = (userId, data) => {
return api("PUT", `${URL}/${userId}`, data);
}

export const patchUser = (userId, data) => {
return api("PATCH", `${URL}/${userId}`, data);
}

export const deleteUser = (userId) => {
return api("DELETE", `${URL}/${userId}`);
}

或你要用 OOP 的方式包裝成 class 也可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { api } from "./api";

const URL = "/users";

export class UserApi {
gerUserList = async (data) => {
return api("GET", URL, data);
}

addUser = async (data) => {
return api("POST", URL, data);
}

putUser = async (userId, data) => {
return api("PUT", `${URL}/${userId}`, data);
}

patchUser = async (userId, data) => {
return api("PATCH", `${URL}/${userId}`, data);
}

deleteUser = async (userId) => {
return api("DELETE", `${URL}/${userId}`);
}
}

最後呢,就可以把我們寫好的東西拿來用了:

1
2
3
4
5
6
7
import { gerUserList } from "./user";

gerUserList({
page: 1
}).then(res => console.log('Get user list', res.data))
.catch(err => console.log('err', err))

class 的寫法:

1
2
3
4
5
6
7
import { UserApi } from "./userByClass";
const api = new UserApi();

api.gerUserList({
page: 1
}).then(res => console.log('Get user list', res.data))
.catch(err => console.log('err', err))

以上,有任何問題的話都可以到這邊參考原始碼

參考資料

來點不一樣的狀態管理 mobx Git 設定個人資訊
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×