axios 是一個輕量的 HTTP客戶端,它基於 XMLHttpRequest 服務來執行 HTTP 請求,支持豐富的配置,支持 Promise,支持瀏覽器端和 Node.js 端。在真實項目中為了提高我們的代碼質量,我們通常會對 axios 二次封裝一下再使用。這篇文章就帶你從零開始封裝 axio... ...
前言
axios 是一個輕量的HTTP客戶端,它基於 XMLHttpRequest 服務來執行 HTTP 請求,支持豐富的配置,支持 Promise,支持瀏覽器端和 Node.js 端。自Vue2.0起,尤大大(Vue作者尤雨溪)宣佈取消對 vue-resource 的官方推薦,轉而推薦 axios。現在 axios 已經成為大部分 Vue 開發者的首選。
(如果你還不熟悉 axios,可以在這裡查看它的API)。
axios 的API很友好,你完全可以很輕鬆地在項目中直接使用。不過隨著項目規模增大,如果每發起一次HTTP請求,就要把這些比如設置超時時間、設置請求頭、根據項目環境判斷使用哪個請求地址、錯誤處理等等操作,都就地寫一遍,得瘋!這種重覆勞動不僅浪費時間,而且讓代碼變得冗餘不堪,難以維護。
為了提高我們的代碼質量,我們應該在項目中二次封裝一下 axios
再使用。
那麼,怎麼封裝 axios
呢?
原來的樣子
封裝前,先來看下,不封裝的情況下,一個實際項目中axios請求的樣子。大概是長這樣:
axios('http://localhost:3000/data', {
method: 'GET',
timeout: 1000,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
Authorization: 'xxx',
},
transformRequest: [function (data, headers) {
return data;
}],
// 其他請求配置...
})
.then((data) => {
// todo: 真正業務邏輯代碼
console.log(data);
}, (err) => {
if (err.response.status === 401) {
// handle authorization error
}
if (err.response.status === 403) {
// handle server forbidden error
}
// 其他錯誤處理.....
console.log(err);
});
可以看到在這段代碼中,頁面代碼邏輯只在第15行處,上方的一大塊請求配置代碼和下方一大塊響應錯誤處理代碼,幾乎跟頁面功能沒有關係,而且每個請求中這些內容都差不多,甚至有的部分完全一樣。想象一下,每發一次請求都來這麼一下,十幾個請求一寫,會是什麼盛況?
封裝步驟
封裝的本質就是在待封裝的內容外面添加各種東西,然後把它們作為一個新的整體呈現給使用者,以達到擴展和易用的目的。
封裝axios
要做的事情,就是把所有HTTP請求共用的配置,事先都在axios上配置好,預留好必要的參數和介面,然後把它作為新的axios返回。
接下來我們藉助一個demo實現一個具有良好擴展性的axios
封裝。
demo目錄結構如下(由Vue-cli 3.0 生成):
|--public/
|--mock/
| |--db.json # 我新建的介面模擬數據
|--src/
| |--assets/
| |--components/
| |--router/
| |--store/
| |--views/
| |--Home.Vue
| |--App.vue
| |--main.js
| |--theme.styl
|--package.json
|...
封裝目標
我希望在 Home 頁,發起 axios 請求時就像調用一個只有少量參數的方法一樣簡單,這樣我就可以專註業務代碼了。
1. 將 axios 封裝到一個獨立的文件
- 在src下創建 utils/http.js 文件
cd src
mkdir utils
touch http.js
- 引入 axios
// src/utils/http.js
import axios from 'axios';
- 創建一個類
你也可以用函數來封裝,我只是覺得類更語義化而已。
//src/utils/http.js
//...
class NewAxios {
}
- 給不同環境配置不同請求地址
根據process.env.NODE_ENV
配置不同的baseURL
,使項目只需執行相應打包命令,就可以在不同環境中自動切換請求主機地址。
// src/utils/http.js
//...
const getBaseUrl = (env) => {
let base = {
production: '/',
development: 'http://localhost:3000',
test: 'http://localhost:3001',
}[env];
if (!base) {
base = '/';
}
return base;
};
class NewAxios {
constructor() {
this.baseURL = getBaseUrl(process.env.NODE_ENV);
}
}
- 配置超時時間
timeout屬性,我一般設置10秒。
// src/utils/http.js
//...
class NewAxios {
constructor() {
//...
this.timeout = 10000;
}
}
- 配置允許攜帶憑證
widthCredentials屬性設為true。
// src/utils/http.js
//...
class NewAxios {
constructor() {
//...
this.withCredentials = true;
}
}
- 給這個類創建實例上的方法request
在request
方法里,創建新的axios實例,接收請求配置參數,處理參數,添加配置,返回axios實例的請求結果(一個promise對象)。
你也可以不創建,直接使用預設導出的axios實例,然後把所有配置都放到它上面,不過這樣一來整個項目就會共用一個axios實例。雖然大部分項目下這樣夠用沒問題,但是有的項目中不同服務地址的請求和響應結構可能完全不同,這個時候共用一個實例就沒辦法支持了。所以為了封裝可以更通用,更具靈活性,我會使用axios的create方法,使每次發請求都是新的axios實例。
// src/utils/http.js
//...
class NewAxios {
//...
request(options) {
// 每次請求都會創建新的axios實例。
const instance = axios.create();
const config = { // 將用戶傳過來的參數與公共配置合併。
...options,
baseURL: this.baseURL,
timeout: this.timeout,
withCredentials: this.withCredentials,
};
// 配置攔截器,支持根據不同url配置不同的攔截器。
this.setInterceptors(instance, options.url);
return instance(config); // 返回axios實例的執行結果
}
}
因為攔截器配置內容比較多,所以封裝成一個內部函數了。
- 配置請求攔截器
在發送請求前對請求參數做的所有修改都在這裡統一配置。比如統一添加token憑證、統一設置語言、統一設置內容類型、指定數據格式等等。做完後記得返回這個配置,否則整個請求不會進行。
我這裡就配置一個token。
// src/utils/http.js
//...
class NewAxios {
//...
// 這裡的url可供你針對需要特殊處理的介面路徑設置不同攔截器。
setInterceptors = (instance, url) => {
instance.interceptors.request.use((config) => { // 請求攔截器
// 配置token
config.headers.AuthorizationToken = localStorage.getItem('AuthorizationToken') || '';
return config;
}, err => Promise.reject(err));
}
//...
}
- 配置響應攔截器
在請求的then
或catch
處理前對響應數據進行一輪預先處理。比如過濾響應數據,更多的,是在這裡對各種響應錯誤碼進行統一錯誤處理,還有斷網處理等等。
我這裡就判斷一下403和斷網。
// src/utils/http.js
//...
class NewAxios {
//...
setInterceptors = (instance, url) => {
//...
instance.interceptors.response.use((response) => { // 響應攔截器
// todo: 想根據業務需要,對響應結果預先處理的,都放在這裡
console.log();
return response;
}, (err) => {
if (err.response) { // 響應錯誤碼處理
switch (err.response.status) {
case '403':
// todo: handler server forbidden error
break;
// todo: handler other status code
default:
break;
}
return Promise.reject(err.response);
}
if (!window.navigator.online) { // 斷網處理
// todo: jump to offline page
return -1;
}
return Promise.reject(err);
});
}
//...
}
另外,在攔截器里,還適合放置loading等緩衝效果:在請求攔截器里顯示loading,在響應攔截器里移除loading。這樣所有請求就都有了一個統一的loading效果。
- 預設導出新的實例
// src/utils/http.js
//...
export default new NewAxios();
最後完整的代碼如下:
// src/utils/http.js
import axios from 'axios';
const getBaseUrl = (env) => {
let base = {
production: '/',
development: 'http://localhost:3000',
test: 'http://localhost:3001',
}[env];
if (!base) {
base = '/';
}
return base;
};
class NewAxios {
constructor() {
this.baseURL = getBaseUrl(process.env.NODE_ENV);
this.timeout = 10000;
this.withCredentials = true;
}
setInterceptors = (instance, url) => {
instance.interceptors.request.use((config) => {
// 在這裡添加loading
// 配置token
config.headers.AuthorizationToken = localStorage.getItem('AuthorizationToken') || '';
return config;
}, err => Promise.reject(err));
instance.interceptors.response.use((response) => {
// 在這裡移除loading
// todo: 想根據業務需要,對響應結果預先處理的,都放在這裡
return response;
}, (err) => {
if (err.response) { // 響應錯誤碼處理
switch (err.response.status) {
case '403':
// todo: handler server forbidden error
break;
// todo: handler other status code
default:
break;
}
return Promise.reject(err.response);
}
if (!window.navigator.online) { // 斷網處理
// todo: jump to offline page
return -1;
}
return Promise.reject(err);
});
}
request(options) {
// 每次請求都會創建新的axios實例。
const instance = axios.create();
const config = { // 將用戶傳過來的參數與公共配置合併。
...options,
baseURL: this.baseURL,
timeout: this.timeout,
withCredentials: this.withCredentials,
};
// 配置攔截器,支持根據不同url配置不同的攔截器。
this.setInterceptors(instance, options.url);
return instance(config); // 返回axios實例的執行結果
}
}
export default new NewAxios();
現在 axios
封裝算是完成了80%。我們還需要再進一步把axios和介面結合再封裝一層,才能達到我在一開始定的封裝目標。
2. 使用新的 axios 封裝API
- 在 src 目錄下新建
api
文件夾。把所有涉及HTTP請求的介面統一集中到這個目錄來管理。 - 新建
home.js
。我們需要把介面根據一定規則分好類,一類介面對應一個js文件。這個分類可以是按頁面來劃分,或者按模塊等等。為了演示更直觀,我這裡就按頁面來劃分了。實際根據自己的需求來定。 - 使用新的 axios 封裝API(固定url的值,合併用戶傳過來的參數),然後命名導出這些函數。
// src/api/home.js
import axios from '@/utils/http';
export const fetchData = options => axios.request({
...options,
url: '/data',
});
export default {};
- 在 api 目錄下新建
index.js
,把其他文件的介面都在這個文件里彙總導出。
// src/api/index.js
export * from './home';
這層封裝將我們的新的axios封裝到了更簡潔更語義化的介面方法中。
現在我們的目錄結構長這樣:
|--public/
|--mock/
| |--db.json # 介面模擬數據
|--src/
| |--api/ # 所有的介面都集中在這個目錄下
| |--home.js # Home頁面里涉及到的介面封裝在這裡
| |--index.js # 項目中所有介面調用的入口
| |--assets/
| |--components/
| |--router/
| |--store/
| |--utils/
| |--http.js # axios封裝在這裡
| |--views/
| |--Home.Vue
| |--App.vue
| |--main.js
| |--theme.styl
|--package.json
|...
使用封裝後的axios
現在我們要發HTTP請求時,只需引入 api
下的 index.js
文件就可以調用任何介面了,並且用的是封裝後的 axios
。
// src/views/Home.vue
<template>
<div class="home">
<h1>This is home page</h1>
</div>
</template>
<script>
// @ is an alias to /src
import { fetchData } from '@/api/index';
export default {
name: 'home',
mounted() {
fetchData() // axios請求在這裡
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
},
};
</script>
axios請求被封裝在fetchData
函數里,頁面請求壓根不需要出現任何axios API
,悄無聲息地發起請求獲取響應,就像在調用一個簡單的 Promise
函數一樣輕鬆。並且在頁面中只需專註處理業務功能,不用被其他事物干擾。
運行
運行 npm run serve
啟動項目,執行 npm run mock
啟動服務mock介面。
現在打開 localhost:8080
可以看到home頁面。打開瀏覽器控制台,可以看到列印的請求響應結果:
簡潔,優雅。
總結
- 封裝思想是前端技術中很有用的思想,簡單的axios及介面封裝,就可以讓我們可以領略到它的魅力。
- 封裝
axios
沒有一個絕對的標準,只要你的封裝可以滿足你的項目需求,並且用起來方便,那就是一個好的封裝方案。 - BTW:以上封裝給大家提供了一個封裝好的axios和api框架,經過以上過程封裝好的
axios
,可以不局限於 Vue,React 項目同樣可以拿去使用,它適用任何前端項目。
本文的代碼可以在這裡獲取:https://github.com/yc111/wrap-axios
歡迎交流~
歡迎轉載,轉載請註明出處:
https://champyin.com/2019/12/23/%E5%B0%81%E8%A3%85axios/