本文整理來(lái)自深入Vue3+TypeScript技術(shù)棧-coderwhy大神新課,只作為個(gè)人筆記記錄使用,請(qǐng)大家多支持王紅元老師。
認(rèn)識(shí)axios
為什么選擇axios? 因?yàn)樽髡咄扑]。

功能特點(diǎn):
- 在瀏覽器中發(fā)送 XMLHttpRequests 請(qǐng)求
- 在 node.js 中發(fā)送 http請(qǐng)求
- 支持 Promise API
- 攔截請(qǐng)求和響應(yīng)
- 轉(zhuǎn)換請(qǐng)求和響應(yīng)數(shù)據(jù)
- 等等
補(bǔ)充:axios名稱的由來(lái)?
個(gè)人理解,沒(méi)有具體的翻譯,axios:ajax i/o system.
axios請(qǐng)求方式
支持多種請(qǐng)求方式:
- axios(config)
- axios.request(config)
- axios.get(url[, config])
- axios.delete(url[, config])
- axios.head(url[, config])
- axios.post(url[, data[, config]])
- axios.put(url[, data[, config]])
- axios.patch(url[, data[, config]])
import axios from 'axios'
// axios的實(shí)例對(duì)象
// get請(qǐng)求
axios.get('http://123.207.32.32:8000/home/multidata').then((res) => {
console.log(res.data)
})
// 額外補(bǔ)充的Promise中類型的使用
// Promise本身是可以有類型
new Promise<string>((resolve) => {
// 泛型指定了,只能傳string
resolve('abc')
}).then((res) => {
// 并且res也是string類型的
console.log(res.length)
}
// 使用http://httpbin.org模擬數(shù)據(jù)請(qǐng)求
// get請(qǐng)求,并且傳入?yún)?shù)
axios
.get('http://httpbin.org/get', {
// get請(qǐng)求使用params傳參,并且最后會(huì)拼接到url后面
params: {
name: 'coderwhy',
age: 18
}
})
.then((res) => {
console.log(res.data)
})
// post請(qǐng)求,傳入?yún)?shù)
axios
.post('http://httpbin.org/post', {
// post請(qǐng)求使用data傳參
data: {
name: 'why',
age: 18
}
})
.then((res) => {
console.log(res.data)
})
有時(shí)候,我們可能需求同時(shí)發(fā)送兩個(gè)請(qǐng)求,使用axios.all, 可以放入多個(gè)請(qǐng)求的數(shù)組,當(dāng)所有請(qǐng)求完成之后,axios.all([]) 會(huì)返回一個(gè)數(shù)組,使用 axios.spread 可將數(shù)組 [res1,res2] 展開為 res1, res2。
// axios.all -> 多個(gè)請(qǐng)求, 一起返回
axios
.all([
axios.get('/get', { params: { name: 'why', age: 18 } }),
axios.post('/post', { data: { name: 'why', age: 18 } })
])
.then((res) => {
// 結(jié)果是個(gè)數(shù)組
console.log(res[0].data)
console.log(res[1].data)
})
axios常見(jiàn)的配置選項(xiàng)
有時(shí)候我們需要配置請(qǐng)求的baseURL和timeout,具體其他配置如下:
| 解釋 | 配置選項(xiàng) |
|---|---|
| 請(qǐng)求地址 | url: '/user' |
| 請(qǐng)求類型 | method: 'get' |
| 請(qǐng)求根路徑 | baseURL: 'http://www.mt.com/api' |
| 請(qǐng)求前的數(shù)據(jù)處理 | transformRequest:[function(data){}] |
| 請(qǐng)求后的數(shù)據(jù)處理 | transformResponse: [function(data){}] |
| 自定義的請(qǐng)求頭 | headers:{'x-Requested-With':'XMLHttpRequest'} |
| URL查詢對(duì)象 | params:{ id: 12 } |
| 查詢對(duì)象序列化函數(shù) | paramsSerializer: function(params){ } |
| request body | data: { key: 'aa'} |
| 超時(shí)設(shè)置 | timeout: 1000 |
| 跨域是否帶Token | withCredentials: false |
| 自定義請(qǐng)求處理 | adapter: function(resolve, reject, config){} |
| 身份驗(yàn)證信息 | auth: { uname: '', pwd: '12'} |
| 響應(yīng)的數(shù)據(jù)格式(json、blob、document、arraybuffer、text、stream) | responseType: 'json' |
// axios的配置選項(xiàng)
// 全局的配置 baseURL timeout headers
axios.defaults.baseURL = 'http://httpbin.org'
axios.defaults.timeout = 10000
// axios.defaults.headers = {}
// 每一個(gè)請(qǐng)求單獨(dú)的配置 timeout headers
axios
.get('/get', {
params: {
name: 'coderwhy',
age: 18
},
// 單獨(dú)配置
timeout: 5000,
headers: {}
})
.then((res) => {
console.log(res.data)
})
// post請(qǐng)求
axios
// 全局配置baseURL之后就不用再寫url了
.post('/post', {
data: {
name: 'why',
age: 18
}
})
.then((res) => {
console.log(res.data)
})
axios的實(shí)例和攔截器
為什么要?jiǎng)?chuàng)建axios的實(shí)例呢?
當(dāng)我們從axios模塊中導(dǎo)入對(duì)象時(shí),使用的實(shí)例是默認(rèn)的實(shí)例,當(dāng)給該實(shí)例設(shè)置一些默認(rèn)配置時(shí),這些配置就被固定下來(lái)了。但是后續(xù)開發(fā)中,某些配置可能會(huì)不太一樣,比如某些請(qǐng)求需要使用特定的baseURL或者timeout或者content-Type等,這個(gè)時(shí)候, 我們就可以創(chuàng)建新的實(shí)例,并且傳入屬于該實(shí)例的配置信息。
axios也可以設(shè)置攔截器,攔截每次請(qǐng)求和響應(yīng):
- axios.interceptors.request.use(請(qǐng)求成功攔截, 請(qǐng)求失敗攔截)
- axios.interceptors.response.use(響應(yīng)成功攔截, 響應(yīng)失敗攔截)
// axios的攔截器
// 參數(shù)fn1: 請(qǐng)求發(fā)送成功會(huì)執(zhí)行的函數(shù)
// 參數(shù)fn2: 請(qǐng)求發(fā)送失敗會(huì)執(zhí)行的函數(shù)
axios.interceptors.request.use(
(config) => {
// 想做的一些操作
// 1.給請(qǐng)求添加token
// 2.添加isLoading動(dòng)畫
console.log('請(qǐng)求成功的攔截')
return config
},
(err) => {
console.log('請(qǐng)求發(fā)送錯(cuò)誤')
return err
}
)
// fn1: 數(shù)據(jù)響應(yīng)成功(服務(wù)器正常的返回了數(shù)據(jù) 200)
// fn2: 數(shù)據(jù)響應(yīng)失敗
axios.interceptors.response.use(
(res) => {
console.log('響應(yīng)成功的攔截')
return res
},
(err) => {
console.log('服務(wù)器響應(yīng)失敗')
return err
}
)
區(qū)分不同環(huán)境
在開發(fā)中,有時(shí)候我們需要根據(jù)不同的環(huán)境設(shè)置不同的環(huán)境變量,常見(jiàn)的有三種環(huán)境:
- 開發(fā)環(huán)境:development;
- 生產(chǎn)環(huán)境:production;
- 測(cè)試環(huán)境:test;
如何區(qū)分環(huán)境變量呢?常見(jiàn)有三種方式:
方式一:手動(dòng)修改不同的變量(不推薦)。
方式二:根據(jù)process.env.NODE_ENV的值進(jìn)行區(qū)分,這種方式也是使用很多的一種方式(推薦)。
// 根據(jù)process.env.NODE_ENV區(qū)分
// 開發(fā)環(huán)境: development
// 生成環(huán)境: production
// 測(cè)試環(huán)境: test
let BASE_URL = ''
const TIME_OUT = 10000
if (process.env.NODE_ENV === 'development') {
BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = 'http://coderwhy.org/prod'
} else {
BASE_URL = 'http://coderwhy.org/test'
}
export { BASE_URL, TIME_OUT }
方式三:編寫不同的環(huán)境變量配置文件,vue cli支持這種方式,我們創(chuàng)建.env.development、.env.production、.env.test文件。
通過(guò)環(huán)境變量配置文件,我們可以給BASE_URL、NODE_DEV設(shè)置值,這些值會(huì)自動(dòng)被注入,如果是我們自定義的名字,可以VUE_APP_XXX這種格式的也可以被注入。
.env.development文件:
VUE_APP_BASE_URL=https://coderwhy.org/dev
VUE_APP_BASE_NAME=coderwhy
.env.production文件:
VUE_APP_BASE_URL=https://coderwhy.org/prod
VUE_APP_BASE_NAME=kobe
.env.test文件:
VUE_APP_BASE_URL=https://coderwhy.org/test
VUE_APP_BASE_NAME=james
在JS中,我們直接使用不會(huì)報(bào)錯(cuò),在TS中直接使用會(huì)報(bào)錯(cuò):
// 報(bào)錯(cuò)
console.log(process.env.VUE_APP_BASE_URL)
我們?cè)趕hims-vue.d.ts文件中聲明一下即可:
declare const VUE_APP_BASE_URL: string
npm run build打包項(xiàng)目,打開打包后的build文件夾下的index.html文件,通過(guò)live serve打開index.html文件,這時(shí)候很多文件是加載不到的:

加載不到的原因是因?yàn)樯厦娴穆窂绞歉鶕?jù)域名拼接的絕對(duì)路徑,我們可以進(jìn)入index.html中,將加載文件的路徑改成相對(duì)路徑:src="./js/chunk-xxxxxx.js",也就是加載當(dāng)前路徑下的js文件夾下的文件,一個(gè)一個(gè)改路徑,比較麻煩。
打包之后,如果不想手動(dòng)一個(gè)一個(gè)改路徑,可以進(jìn)入vue.config.js文件中,添加publicPath: './',這個(gè)值其實(shí)就是修改加載資源的路徑,但是部署到服務(wù)器的時(shí)候肯定不需要這個(gè)值了,注釋掉即可,或者加個(gè)環(huán)境判斷也可以。
封裝axios
先講一下邏輯,我們將其封裝成一個(gè)對(duì)象(為什么封裝成類呢?因?yàn)槭褂妙惙庋b性更強(qiáng)一點(diǎn)),新建service文件夾,service文件夾里面再新建request文件夾和index.ts文件,然后在main.ts里面引入這個(gè)對(duì)象,然后就可以使用了,文件目錄如下:

首先要安裝axios:
npm install axios
先說(shuō)一下我們封裝要達(dá)到的目的:可以對(duì)某個(gè)請(qǐng)求、某個(gè)請(qǐng)求實(shí)例的所有請(qǐng)求、所有請(qǐng)求實(shí)例的所有請(qǐng)求,設(shè)置攔截和是否顯示loading。
下面就對(duì)每個(gè)文件的代碼以及作用進(jìn)行講解:
config.ts代碼如下,會(huì)根據(jù)環(huán)境配置不同的BASE_URL。
// 根據(jù)process.env.NODE_ENV區(qū)分
// 開發(fā)環(huán)境: development
// 生成環(huán)境: production
// 測(cè)試環(huán)境: test
let BASE_URL = ''
const TIME_OUT = 10000
if (process.env.NODE_ENV === 'development') {
BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = 'http://coderwhy.org/prod'
} else {
BASE_URL = 'http://coderwhy.org/test'
}
export { BASE_URL, TIME_OUT }
在type.js里面我們定義一個(gè)接口,用于規(guī)定創(chuàng)建請(qǐng)求實(shí)例或者調(diào)用request方法的時(shí)候傳入的參數(shù)是什么樣的,代碼如下:
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
// 定義一個(gè)接口,表示這個(gè)接口的實(shí)例要有這4個(gè)屬性,當(dāng)然不是必須的,是可選的
// 傳入一個(gè)泛型,默認(rèn)值是AxiosResponse
export interface HYRequestInterceptors<T = AxiosResponse> {
// 攔截器都是可選的
// 請(qǐng)求攔截
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
// 請(qǐng)求錯(cuò)誤攔截
requestInterceptorCatch?: (error: any) => any
// 響應(yīng)攔截
// 由于我們?cè)谇懊嬷苯訉es.data返回了,所以這里如果傳入了T,那么返回的類型就是傳入的T
responseInterceptor?: (res: T) => T
// 響應(yīng)錯(cuò)誤攔截
responseInterceptorCatch?: (error: any) => any
}
// 定義一個(gè)新的接口,繼承于AxiosRequestConfig,表示我們傳入的參數(shù)要有interceptors和showLoading,當(dāng)然也是可選的
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
// 對(duì)原來(lái)的AxiosRequestConfig進(jìn)行擴(kuò)展,添加攔截器和是否顯示loading,可選的
interceptors?: HYRequestInterceptors<T>
showLoading?: boolean
}
核心代碼就是request文件夾下的index.js文件,代碼如下:
import axios from 'axios'
// 導(dǎo)入axios實(shí)例的類型
import type { AxiosInstance } from 'axios'
import type { HYRequestInterceptors, HYRequestConfig } from './type'
// 引入loading組件
import { ElLoading } from 'element-plus'
// 引入loading組件的類型
import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type'
// 默認(rèn)顯示loading
const DEAFULT_LOADING = true
class HYRequest {
// axios實(shí)例
instance: AxiosInstance
// 當(dāng)前請(qǐng)求實(shí)例的攔截器
interceptors?: HYRequestInterceptors
// 是否顯示loading
showLoading: boolean
// 保存的loading實(shí)例
loading?: ILoadingInstance
constructor(config: HYRequestConfig) {
// 創(chuàng)建axios實(shí)例
this.instance = axios.create(config)
// 保存基本信息
this.interceptors = config.interceptors
this.showLoading = config.showLoading ?? DEAFULT_LOADING
// 使用攔截器
// 1.從config中取出的攔截器是對(duì)應(yīng)的實(shí)例的攔截器
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
// 2.添加所有的實(shí)例都有的攔截器
// 請(qǐng)求的時(shí)候,先添加的攔截器后執(zhí)行
// 響應(yīng)的時(shí)候,先添加的攔截器先執(zhí)行
this.instance.interceptors.request.use(
(config) => {
console.log('所有的實(shí)例都有的攔截器: 請(qǐng)求成功攔截')
// 所有的請(qǐng)求都添加loading
if (this.showLoading) {
// 添加loading
this.loading = ElLoading.service({
lock: true,
text: '正在請(qǐng)求數(shù)據(jù)....',
background: 'rgba(0, 0, 0, 0.5)'
})
}
return config
},
(err) => {
console.log('所有的實(shí)例都有的攔截器: 請(qǐng)求失敗攔截')
return err
}
)
this.instance.interceptors.response.use(
(res) => {
console.log('所有的實(shí)例都有的攔截器: 響應(yīng)成功攔截')
// 所有的請(qǐng)求,將loading移除
this.loading?.close()
// 因?yàn)槲覀冃枰木褪莚es.data,所以我們可以在所有請(qǐng)求實(shí)例的請(qǐng)求的響應(yīng)攔截器里面,直接把res.data返回,這樣我們就可以直接使用了
const data = res.data
// 判斷當(dāng)HttpErrorCode是200的時(shí)候,服務(wù)端和客戶端一塊自定義的錯(cuò)誤信息
if (data.returnCode === '-1001') {
console.log('請(qǐng)求失敗~, 錯(cuò)誤信息')
} else {
return data
}
},
(err) => {
console.log('所有的實(shí)例都有的攔截器: 響應(yīng)失敗攔截')
// 所有的請(qǐng)求,將loading移除
this.loading?.close()
// 判斷不同的HttpErrorCode顯示不同的錯(cuò)誤信息
if (err.response.status === 404) {
console.log('404的錯(cuò)誤~')
}
return err
}
)
}
// 1.傳入返回結(jié)果的類型T,這樣在Promise中我們就知道返回值的類型是T了
// 2.通過(guò)HYRequestConfig<T>,將返回值類型T告訴接口,從而在接口的返回響應(yīng)攔截中指明返回值類型就是T
request<T>(config: HYRequestConfig<T>): Promise<T> {
// 返回一個(gè)Promise對(duì)象,好讓使用者在外面拿到數(shù)據(jù)
return new Promise((resolve, reject) => {
// 1.單個(gè)請(qǐng)求對(duì)請(qǐng)求config的處理
if (config.interceptors?.requestInterceptor) {
// 如果有單個(gè)請(qǐng)求的攔截器,就執(zhí)行一下這個(gè)函數(shù),然后返回
config = config.interceptors.requestInterceptor(config)
}
// 2.判斷單個(gè)請(qǐng)求是否需要顯示loading
if (config.showLoading === false) {
this.showLoading = config.showLoading
}
this.instance
// request里面有兩個(gè)泛型,第一個(gè)泛型默認(rèn)是any,第二個(gè)泛型是AxiosResponse
// 由于前面我們已經(jīng)將res.data直接返回了,所以其實(shí)最后的數(shù)據(jù)就是T類型的,所以我們?cè)诘诙€(gè)泛型中要指定返回值的類型T
.request<any, T>(config)
.then((res) => {
// 1.單個(gè)請(qǐng)求對(duì)數(shù)據(jù)的處理
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
// 2.將showLoading設(shè)置true, 這樣不會(huì)影響下一個(gè)請(qǐng)求
this.showLoading = DEAFULT_LOADING
// 3.將結(jié)果resolve返回出去
resolve(res)
})
.catch((err) => {
// 將showLoading設(shè)置true, 這樣不會(huì)影響下一個(gè)請(qǐng)求
this.showLoading = DEAFULT_LOADING
reject(err)
return err
})
})
}
get<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'GET' })
}
post<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'POST' })
}
delete<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE' })
}
patch<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'PATCH' })
}
}
export default HYRequest
這時(shí)候我們需要?jiǎng)?chuàng)建一個(gè)請(qǐng)求實(shí)例,用于發(fā)送網(wǎng)絡(luò)請(qǐng)求,當(dāng)然我們也可以創(chuàng)建不止一個(gè)請(qǐng)求實(shí)例,然后設(shè)置不同的baseurl、超時(shí)時(shí)間、攔截器等等,這里我們只創(chuàng)建一個(gè),所以外層的index.ts代碼如下:
// service統(tǒng)一出口
import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'
// 創(chuàng)建一個(gè)新的請(qǐng)求,并傳入?yún)?shù)
const hyRequest = new HYRequest({
// 傳入baseurl
baseURL: BASE_URL,
// 傳入超時(shí)時(shí)間
timeout: TIME_OUT,
// 傳入攔截器
interceptors: {
requestInterceptor: (config) => {
// 給當(dāng)前請(qǐng)求實(shí)例所有的請(qǐng)求添加token
const token = ''
if (token) {
// 模板字符串進(jìn)行拼接
config.headers.Authorization = `Bearer ${token}`
}
console.log('請(qǐng)求成功的攔截')
return config
},
requestInterceptorCatch: (err) => {
console.log('請(qǐng)求失敗的攔截')
return err
},
responseInterceptor: (res) => {
console.log('響應(yīng)成功的攔截')
return res
},
responseInterceptorCatch: (err) => {
console.log('響應(yīng)失敗的攔截')
return err
}
}
})
export default hyRequest
在main.ts中使用如下:
import { createApp } from 'vue'
import App from './App.vue'
// 導(dǎo)入請(qǐng)求實(shí)例
import hyRequest from './service'
const app = createApp(App)
app.mount('#app')
hyRequest.request({
url: '/home/multidata',
method: 'GET',
headers: {},
interceptors: {
requestInterceptor: (config) => {
console.log('單獨(dú)請(qǐng)求的config')
config.headers['token'] = '123'
return config
},
responseInterceptor: (res) => {
console.log('單獨(dú)響應(yīng)的response')
return res
}
}
})
// 定義返回結(jié)果的類型
interface DataType {
data: any
returnCode: string
success: boolean
}
// 只有請(qǐng)求者才知道返回結(jié)果的類型
hyRequest
.get<DataType>({
url: '/home/multidata',
showLoading: false
})
// 這時(shí)候這里的res就是DataType類型的
.then((res) => {
console.log(res.data)
console.log(res.returnCode)
console.log(res.success)
})
注意:Vuex和TS的結(jié)合比較難用,所以如果使用TS,我們一般使用pinia這個(gè)庫(kù)來(lái)代替Vuex。