<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
雖然說Fetch API已經使用率已經非常的高了,但是在一些老的瀏覽器還是不支援的,而且axios仍然每週都保持2000多萬的下載量,這就說明了axios仍然存在不可撼動的地位,接下來我們就一步一步的去封裝,實現一個靈活、可複用的一個請求請發。
這篇文章封裝的axios已經滿足如下功能:
首先我們實現一個最基本的版本,範例程式碼如下:
// index.ts import axios from 'axios' import type { AxiosInstance, AxiosRequestConfig } from 'axios' class Request { // axios 範例 instance: AxiosInstance constructor(config: AxiosRequestConfig) { this.instance = axios.create(config) } request(config: AxiosRequestConfig) { return this.instance.request(config) } } export default Request
這裡將其封裝為一個類,而不是一個函數的原因是因為類可以建立多個範例,適用範圍更廣,封裝性更強一些。
首先我們封裝一下攔截器,這個攔截器分為三種:
接下來我們就分別實現這三個攔截器。
類攔截器比較容易實現,只需要在類中對axios.create()
建立的範例呼叫interceptors
下的兩個攔截器即可,範例程式碼如下:
// index.ts constructor(config: AxiosRequestConfig) { this.instance = axios.create(config) this.instance.interceptors.request.use( (res: AxiosRequestConfig) => { console.log('全域性請求攔截器') return res }, (err: any) => err, ) this.instance.interceptors.response.use( // 因為我們介面的資料都在res.data下,所以我們直接返回res.data (res: AxiosResponse) => { console.log('全域性響應攔截器') return res.data }, (err: any) => err, ) }
我們在這裡對響應攔截器做了一個簡單的處理,就是將請求結果中的.data
進行返回,因為我們對介面請求的資料主要是存在在.data
中,跟data
同級的屬性我們基本是不需要的。
範例攔截器是為了保證封裝的靈活性,因為每一個範例中的攔截後處理的操作可能是不一樣的,所以在定義範例時,允許我們傳入攔截器。
首先我們定義一下interface,方便型別提示,程式碼如下:
// types.ts import type { AxiosRequestConfig, AxiosResponse } from 'axios' export interface RequestInterceptors { // 請求攔截 requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig requestInterceptorsCatch?: (err: any) => any // 響應攔截 responseInterceptors?: (config: AxiosResponse) => AxiosResponse responseInterceptorsCatch?: (err: any) => any } // 自定義傳入的引數 export interface RequestConfig extends AxiosRequestConfig { interceptors?: RequestInterceptors }
定義好基礎的攔截器後,我們需要改造我們傳入的引數的型別,因為axios提供的AxiosRequestConfig
是不允許我們傳入攔截器的,所以說我們自定義了RequestConfig
,讓其繼承與AxiosRequestConfig
。
剩餘部分的程式碼也比較簡單,如下所示:
// index.ts import axios, { AxiosResponse } from 'axios' import type { AxiosInstance, AxiosRequestConfig } from 'axios' import type { RequestConfig, RequestInterceptors } from './types' class Request { // axios 範例 instance: AxiosInstance // 攔截器物件 interceptorsObj?: RequestInterceptors constructor(config: RequestConfig) { this.instance = axios.create(config) this.interceptorsObj = config.interceptors this.instance.interceptors.request.use( (res: AxiosRequestConfig) => { console.log('全域性請求攔截器') return res }, (err: any) => err, ) // 使用範例攔截器 this.instance.interceptors.request.use( this.interceptorsObj?.requestInterceptors, this.interceptorsObj?.requestInterceptorsCatch, ) this.instance.interceptors.response.use( this.interceptorsObj?.responseInterceptors, this.interceptorsObj?.responseInterceptorsCatch, ) // 全域性響應攔截器保證最後執行 this.instance.interceptors.response.use( // 因為我們介面的資料都在res.data下,所以我們直接返回res.data (res: AxiosResponse) => { console.log('全域性響應攔截器') return res.data }, (err: any) => err, ) } }
我們的攔截器的執行順序為範例請求→類請求→範例響應→類響應;這樣我們就可以在範例攔截上做出一些不同的攔截,
現在我們對單一介面進行攔截操作,首先我們將AxiosRequestConfig
型別修改為RequestConfig
允許傳遞攔截器;
然後我們在類攔截器中將介面請求的資料進行了返回,也就是說在request()
方法中得到的型別就不是AxiosResponse
型別了。
我們檢視axios的index.d.ts
中,對request()
方法的型別定義如下:
// type.ts request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
也就是說它允許我們傳遞型別,從而改變request()
方法的返回值型別,我們的程式碼如下:
// index.ts request<T>(config: RequestConfig): Promise<T> { return new Promise((resolve, reject) => { // 如果我們為單個請求設定攔截器,這裡使用單個請求的攔截器 if (config.interceptors?.requestInterceptors) { config = config.interceptors.requestInterceptors(config) } this.instance .request<any, T>(config) .then(res => { // 如果我們為單個響應設定攔截器,這裡使用單個響應的攔截器 if (config.interceptors?.responseInterceptors) { res = config.interceptors.responseInterceptors<T>(res) } resolve(res) }) .catch((err: any) => { reject(err) }) }) }
這裡還存在一個細節,就是我們在攔截器接受的型別一直是AxiosResponse
型別,而在類攔截器中已經將返回的型別改變,所以說我們需要為攔截器傳遞一個泛型,從而使用這種變化,修改types.ts
中的程式碼,範例如下:
// index.ts export interface RequestInterceptors { // 請求攔截 requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig requestInterceptorsCatch?: (err: any) => any // 響應攔截 responseInterceptors?: <T = AxiosResponse>(config: T) => T responseInterceptorsCatch?: (err: any) => any }
請求介面攔截是最前執行,而響應攔截是最後執行。
現在我們就來封裝一個請求方法,首先是類進行範例化範例程式碼如下:
// index.ts import Request from './request' const request = new Request({ baseURL: import.meta.env.BASE_URL, timeout: 1000 * 60 * 5, interceptors: { // 請求攔截器 requestInterceptors: config => { console.log('範例請求攔截器') return config }, // 響應攔截器 responseInterceptors: result => { console.log('範例響應攔截器') return result }, }, })
然後我們封裝一個請求方法, 來傳送網路請求,程式碼如下:
// src/server/index.ts import Request from './request' import type { RequestConfig } from './request/types' interface YWZRequestConfig<T> extends RequestConfig { data?: T } interface YWZResponse<T> { code: number message: string data: T } /** * @description: 函數的描述 * @interface D 請求引數的interface * @interface T 響應結構的intercept * @param {YWZRequestConfig} config 不管是GET還是POST請求都使用data * @returns {Promise} */ const ywzRequest = <D, T = any>(config: YWZRequestConfig<D>) => { const { method = 'GET' } = config if (method === 'get' || method === 'GET') { config.params = config.data } return request.request<YWZResponse<T>>(config) } export default ywzRequest
該請求方式預設為GET,且一直用data
作為引數;
應評論區@Pic
、@Michaelee
和@Alone_Error
的建議,這裡增加了一個取消請求;關於什麼是取消請求可以參考官方檔案。
我們需要將所有請求的取消方法儲存到一個集合(這裡我用的陣列,也可以使用Map)中,然後根據具體需要去呼叫這個集合中的某個取消請求方法。
首先定義兩個集合,範例程式碼如下:
// index.ts import type { RequestConfig, RequestInterceptors, CancelRequestSource, } from './types' class Request { /* 存放取消方法的集合 * 在建立請求後將取消請求方法 push 到該集合中 * 封裝一個方法,可以取消請求,傳入 url: string|string[] * 在請求之前判斷同一URL是否存在,如果存在就取消請求 */ cancelRequestSourceList?: CancelRequestSource[] /* 存放所有請求URL的集合 * 請求之前需要將url push到該集合中 * 請求完畢後將url從集合中刪除 * 新增在傳送請求之前完成,刪除在響應之後刪除 */ requestUrlList?: string[] constructor(config: RequestConfig) { // 資料初始化 this.requestUrlList = [] this.cancelRequestSourceList = [] } }
這裡用的CancelRequestSource
介面,我們去定義一下:
// type.ts export interface CancelRequestSource { [index: string]: () => void }
這裡的key
是不固定的,因為我們使用url
做key
,只有在使用的時候才知道url
,所以這裡使用這種語法。
首先我們改造一下request()
方法,它需要完成兩個工作,一個就是在請求之前將url
和取消請求方法push
到我們前面定義的兩個屬性中,然後在請求完畢後(不管是失敗還是成功)都將其進行刪除,實現程式碼如下:
// index.ts request<T>(config: RequestConfig): Promise<T> { return new Promise((resolve, reject) => { // 如果我們為單個請求設定攔截器,這裡使用單個請求的攔截器 if (config.interceptors?.requestInterceptors) { config = config.interceptors.requestInterceptors(config) } const url = config.url // url存在儲存取消請求方法和當前請求url if (url) { this.requestUrlList?.push(url) config.cancelToken = new axios.CancelToken(c => { this.cancelRequestSourceList?.push({ [url]: c, }) }) } this.instance .request<any, T>(config) .then(res => { // 如果我們為單個響應設定攔截器,這裡使用單個響應的攔截器 if (config.interceptors?.responseInterceptors) { res = config.interceptors.responseInterceptors<T>(res) } resolve(res) }) .catch((err: any) => { reject(err) }) .finally(() => { url && this.delUrl(url) }) }) }
這裡我們將刪除操作進行了抽離,將其封裝為一個私有方法,範例程式碼如下:
// index.ts /** * @description: 獲取指定 url 在 cancelRequestSourceList 中的索引 * @param {string} url * @returns {number} 索引位置 */ private getSourceIndex(url: string): number { return this.cancelRequestSourceList?.findIndex( (item: CancelRequestSource) => { return Object.keys(item)[0] === url }, ) as number } /** * @description: 刪除 requestUrlList 和 cancelRequestSourceList * @param {string} url * @returns {*} */ private delUrl(url: string) { const urlIndex = this.requestUrlList?.findIndex(u => u === url) const sourceIndex = this.getSourceIndex(url) // 刪除url和cancel方法 urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1) sourceIndex !== -1 && this.cancelRequestSourceList?.splice(sourceIndex as number, 1) }
現在我們就可以封裝取消請求和取消全部請求了,我們先來封裝一下取消全部請求吧,這個比較簡單,只需要呼叫this.cancelRequestSourceList
中的所有方法即可,實現程式碼如下:
// index.ts // 取消全部請求 cancelAllRequest() { this.cancelRequestSourceList?.forEach(source => { const key = Object.keys(source)[0] source[key]() }) }
現在我們封裝一下取消請求,因為它可以取消一個和多個,那它的引數就是url
,或者包含多個URL的陣列,然後根據傳值的不同去執行不同的操作,實現程式碼如下:
// index.ts // 取消請求 cancelRequest(url: string | string[]) { if (typeof url === 'string') { // 取消單個請求 const sourceIndex = this.getSourceIndex(url) sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url]() } else { // 存在多個需要取消請求的地址 url.forEach(u => { const sourceIndex = this.getSourceIndex(u) sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u]() }) } }
現在我們就來測試一下這個請求方法,這裡我們使用www.apishop.net/提供的免費API進行測試,測試程式碼如下:
<script setup lang="ts"> // app.vue import request from './service' import { onMounted } from 'vue' interface Req { apiKey: string area?: string areaID?: string } interface Res { area: string areaCode: string areaid: string dayList: any[] } const get15DaysWeatherByArea = (data: Req) => { return request<Req, Res>({ url: '/api/common/weather/get15DaysWeatherByArea', method: 'GET', data, interceptors: { requestInterceptors(res) { console.log('介面請求攔截') return res }, responseInterceptors(result) { console.log('介面響應攔截') return result }, }, }) } onMounted(async () => { const res = await get15DaysWeatherByArea({ apiKey: import.meta.env.VITE_APP_KEY, area: '北京市', }) console.log(res.result.dayList) }) </script>
如果在實際開發中可以將這些程式碼分別抽離。
上面的程式碼在命令中輸出
介面請求攔截
範例請求攔截器
全域性請求攔截器
範例響應攔截器
全域性響應攔截器
介面響應攔截
[{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
首先我們在.server/index.ts
中對取消請求方法進行匯出,實現程式碼如下:
// 取消請求 export const cancelRequest = (url: string | string[]) => { return request.cancelRequest(url) } // 取消全部請求 export const cancelAllRequest = () => { return request.cancelAllRequest() }
然後我們在app.vue
中對其進行參照,實現程式碼如下:
<template> <el-button @click="cancelRequest('/api/common/weather/get15DaysWeatherByArea')" >取消請求</el-button > <el-button @click="cancelAllRequest">取消全部請求</el-button> <router-view></router-view> </template> <script setup lang="ts"> import request, { cancelRequest, cancelAllRequest } from './service' </script>
傳送請求後,點選按鈕即可實現對應的功能
本篇文章到這裡就結束了,如果文章對你有用,可以三連支援一下,如果文章中有錯誤或者說你有更好的見解,歡迎指正~
專案地址:ywanzhou/vue3-template (github.com)
以上就是專案中使用Typescript封裝axios的詳細內容,更多關於Typescript封裝axios的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45