1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        在項目中用TS封裝axios,一次封裝團隊受益

        共 10622字,需瀏覽 22分鐘

         ·

        2022-03-16 02:14

        作者:一碗周
        原文:https://juejin.cn/post/7071518211392405541

        寫在前面

        雖然說Fetch API已經(jīng)使用率已經(jīng)非常的高了,但是在一些老的瀏覽器還是不支持的,而且axios仍然每周都保持2000多萬的下載量,這就說明了axios仍然存在不可撼動的地位,接下來我們就一步一步的去封裝,實現(xiàn)一個靈活、可復(fù)用的一個請求請發(fā)。

        這篇文章封裝的axios已經(jīng)滿足如下功能:

        • 無處不在的代碼提示;
        • 靈活的攔截器;
        • 可以創(chuàng)建多個實例,靈活根據(jù)項目進行調(diào)整;
        • 每個實例,或者說每個接口都可以靈活配置請求頭、超時時間等;
        • 取消請求(可以根據(jù)url取消單個請求也可以取消全部請求)。

        基礎(chǔ)封裝

        首先我們實現(xiàn)一個最基本的版本,實例代碼如下:

        //?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

        這里將其封裝為一個類,而不是一個函數(shù)的原因是因為類可以創(chuàng)建多個實例,適用范圍更廣,封裝性更強一些。

        攔截器封裝

        首先我們封裝一下攔截器,這個攔截器分為三種:

        • 類攔截器
        • 實例攔截器
        • 接口攔截器

        接下來我們就分別實現(xiàn)這三個攔截器。

        類攔截器

        類攔截器比較容易實現(xiàn),只需要在類中對axios.create()創(chuàng)建的實例調(diào)用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(
        ????//?因為我們接口的數(shù)據(jù)都在res.data下,所以我們直接返回res.data
        ????(res:?AxiosResponse)?=>?{
        ??????console.log('全局響應(yīng)攔截器')
        ??????return?res.data
        ????},
        ????(err:?any)?=>?err,
        ??)
        }

        我們在這里對響應(yīng)攔截器做了一個簡單的處理,就是將請求結(jié)果中的.data進行返回,因為我們對接口請求的數(shù)據(jù)主要是存在在.data中,跟data同級的屬性我們基本是不需要的。

        實例攔截器

        實例攔截器是為了保證封裝的靈活性,因為每一個實例中的攔截后處理的操作可能是不一樣的,所以在定義實例時,允許我們傳入攔截器。

        首先我們定義一下interface,方便類型提示,代碼如下:

        //?types.ts
        import?type?{?AxiosRequestConfig,?AxiosResponse?}?from?'axios'
        export?interface?RequestInterceptors?{
        ??//?請求攔截
        ??requestInterceptors?:?(config:?AxiosRequestConfig)?=>?AxiosRequestConfig
        ??requestInterceptorsCatch?:?(err:?any)?=>?any
        ??//?響應(yīng)攔截
        ??responseInterceptors?:?(config:?AxiosResponse)?=>?AxiosResponse
        ??responseInterceptorsCatch?:?(err:?any)?=>?any
        }
        //?自定義傳入的參數(shù)
        export?interface?RequestConfig?extends?AxiosRequestConfig?{
        ??interceptors?:?RequestInterceptors
        }

        定義好基礎(chǔ)的攔截器后,我們需要改造我們傳入的參數(shù)的類型,因為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,
        ????)
        ????//?全局響應(yīng)攔截器保證最后執(zhí)行
        ????this.instance.interceptors.response.use(
        ??????//?因為我們接口的數(shù)據(jù)都在res.data下,所以我們直接返回res.data
        ??????(res:?AxiosResponse)?=>?{
        ????????console.log('全局響應(yīng)攔截器')
        ????????return?res.data
        ??????},
        ??????(err:?any)?=>?err,
        ????)
        ??}
        }

        我們的攔截器的執(zhí)行順序為實例請求→類請求→實例響應(yīng)→類響應(yīng);這樣我們就可以在實例攔截上做出一些不同的攔截,

        接口攔截

        現(xiàn)在我們對單一接口進行攔截操作,首先我們將AxiosRequestConfig類型修改為RequestConfig允許傳遞攔截器;然后我們在類攔截器中將接口請求的數(shù)據(jù)進行了返回,也就是說在request()方法中得到的類型就不是AxiosResponse類型了。

        我們查看axios的index.d.ts中,對request()方法的類型定義如下:

        //?type.ts
        requestany,?R?=?AxiosResponse,?D?=?any>(config:?AxiosRequestConfig):?Promise;

        也就是說它允許我們傳遞類型,從而改變request()方法的返回值類型,我們的代碼如下:

        //?index.ts
        request(config:?RequestConfig):?Promise?{
        ??return?new?Promise((resolve,?reject)?=>?{
        ????//?如果我們?yōu)閱蝹€請求設(shè)置攔截器,這里使用單個請求的攔截器
        ????if?(config.interceptors?.requestInterceptors)?{
        ??????config?=?config.interceptors.requestInterceptors(config)
        ????}
        ????this.instance
        ??????.request<any,?T>(config)
        ??????.then(res?=>?{
        ????????//?如果我們?yōu)閱蝹€響應(yīng)設(shè)置攔截器,這里使用單個響應(yīng)的攔截器
        ????????if?(config.interceptors?.responseInterceptors)?{
        ??????????res?=?config.interceptors.responseInterceptors(res)
        ????????}

        ????????resolve(res)
        ??????})
        ??????.catch((err:?any)?=>?{
        ????????reject(err)
        ??????})
        ??})
        }

        這里還存在一個細節(jié),就是我們在攔截器接受的類型一直是AxiosResponse類型,而在類攔截器中已經(jīng)將返回的類型改變,所以說我們需要為攔截器傳遞一個泛型,從而使用這種變化,修改types.ts中的代碼,示例如下:

        //?index.ts
        export?interface?RequestInterceptors?{
        ??//?請求攔截
        ??requestInterceptors?:?(config:?AxiosRequestConfig)?=>?AxiosRequestConfig
        ??requestInterceptorsCatch?:?(err:?any)?=>?any
        ??//?響應(yīng)攔截
        ??responseInterceptors?:?(config:?T)?=>?T
        ??responseInterceptorsCatch?:?(err:?any)?=>?any
        }

        請求接口攔截是最前執(zhí)行,而響應(yīng)攔截是最后執(zhí)行。

        封裝請求方法

        現(xiàn)在我們就來封裝一個請求方法,首先是類進行實例化示例代碼如下:

        //?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
        ????},
        ????//?響應(yīng)攔截器
        ????responseInterceptors:?result?=>?{
        ??????console.log('實例響應(yīng)攔截器')
        ??????return?result
        ????},
        ??},
        })

        然后我們封裝一個請求方法, 來發(fā)送網(wǎng)絡(luò)請求,代碼如下:

        //?src/server/index.ts
        import?Request?from?'./request'

        import?type?{?RequestConfig?}?from?'./request/types'
        interface?YWZRequestConfig?extends?RequestConfig?{
        ??data?:?T
        }
        interface?YWZResponse?{
        ??code:?number
        ??message:?string
        ??data:?T
        }

        /**
        ?*?@description:?函數(shù)的描述
        ?*?@interface?D?請求參數(shù)的interface
        ?*?@interface?T?響應(yīng)結(jié)構(gòu)的intercept
        ?*?@param?{YWZRequestConfig}?config?不管是GET還是POST請求都使用data
        ?*?@returns?{Promise}
        ?*/

        const?ywzRequest?=?any>(config:?YWZRequestConfig)?=>?{
        ??const?{?method?=?'GET'?}?=?config
        ??if?(method?===?'get'?||?method?===?'GET')?{
        ????config.params?=?config.data
        ??}
        ??return?request.request>(config)
        }

        export?default?ywzRequest

        該請求方式默認為GET,且一直用data作為參數(shù);

        取消請求

        應(yīng)評論區(qū)@Pic、@Michaelee@Alone_Error的建議,這里增加了一個取消請求;關(guān)于什么是取消請求可以參考官方文檔[1]。

        準(zhǔn)備工作

        我們需要將所有請求的取消方法保存到一個集合(這里我用的數(shù)組,也可以使用Map)中,然后根據(jù)具體需要去調(diào)用這個集合中的某個取消請求方法。

        首先定義兩個集合,示例代碼如下:

        //?index.ts
        import?type?{
        ??RequestConfig,
        ??RequestInterceptors,
        ??CancelRequestSource,
        }?from?'./types'

        class?Request?{
        ??/*
        ??存放取消方法的集合
        ??*?在創(chuàng)建請求后將取消請求方法?push?到該集合中
        ??*?封裝一個方法,可以取消請求,傳入?url:?string|string[]
        ??*?在請求之前判斷同一URL是否存在,如果存在就取消請求
        ??*/

        ??cancelRequestSourceList?:?CancelRequestSource[]
        ??/*
        ??存放所有請求URL的集合
        ??*?請求之前需要將url?push到該集合中
        ??*?請求完畢后將url從集合中刪除
        ??*?添加在發(fā)送請求之前完成,刪除在響應(yīng)之后刪除
        ??*/

        ??requestUrlList?:?string[]

        ??constructor(config:?RequestConfig)?{
        ????//?數(shù)據(jù)初始化
        ????this.requestUrlList?=?[]
        ????this.cancelRequestSourceList?=?[]
        ??}
        }

        這里用的CancelRequestSource接口,我們?nèi)ザx一下:

        //?type.ts
        export?interface?CancelRequestSource?{
        ??[index:?string]:?()?=>?void
        }

        這里的key是不固定的,因為我們使用urlkey,只有在使用的時候才知道url,所以這里使用這種語法。

        取消請求方法的添加與刪除

        首先我們改造一下request()方法,它需要完成兩個工作,一個就是在請求之前將url和取消請求方法push到我們前面定義的兩個屬性中,然后在請求完畢后(不管是失敗還是成功)都將其進行刪除,實現(xiàn)代碼如下:

        //?index.ts
        request(config:?RequestConfig):?Promise?{
        ??return?new?Promise((resolve,?reject)?=>?{
        ????//?如果我們?yōu)閱蝹€請求設(shè)置攔截器,這里使用單個請求的攔截器
        ????if?(config.interceptors?.requestInterceptors)?{
        ??????config?=?config.interceptors.requestInterceptors(config)
        ????}
        ????const?url?=?config.url
        ????//?url存在保存取消請求方法和當(dāng)前請求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?=>?{
        ????????//?如果我們?yōu)閱蝹€響應(yīng)設(shè)置攔截器,這里使用單個響應(yīng)的攔截器
        ????????if?(config.interceptors?.responseInterceptors)?{
        ??????????res?=?config.interceptors.responseInterceptors(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)
        }

        取消請求方法

        現(xiàn)在我們就可以封裝取消請求和取消全部請求了,我們先來封裝一下取消全部請求吧,這個比較簡單,只需要調(diào)用this.cancelRequestSourceList中的所有方法即可,實現(xiàn)代碼如下:

        //?index.ts
        //?取消全部請求
        cancelAllRequest()?{
        ??this.cancelRequestSourceList?.forEach(source?=>?{
        ????const?key?=?Object.keys(source)[0]
        ????source[key](?"key")
        ??})
        }

        現(xiàn)在我們封裝一下取消請求,因為它可以取消一個和多個,那它的參數(shù)就是url,或者包含多個URL的數(shù)組,然后根據(jù)傳值的不同去執(zhí)行不同的操作,實現(xiàn)代碼如下:

        //?index.ts
        //?取消請求
        cancelRequest(url:?string?|?string[])?{
        ??if?(typeof?url?===?'string')?{
        ????//?取消單個請求
        ????const?sourceIndex?=?this.getSourceIndex(url)
        ????sourceIndex?>=?0?&&?this.cancelRequestSourceList?.[sourceIndex][url](?"sourceIndex][url")
        ??}?else?{
        ????//?存在多個需要取消請求的地址
        ????url.forEach(u?=>?{
        ??????const?sourceIndex?=?this.getSourceIndex(u)
        ??????sourceIndex?>=?0?&&?this.cancelRequestSourceList?.[sourceIndex][u](?"sourceIndex][u")
        ????})
        ??}
        }

        測試

        測試請求方法

        現(xiàn)在我們就來測試一下這個請求方法,這里我們使用www.apishop.net/[2]提供的免費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({
        ????url:?'/api/common/weather/get15DaysWeatherByArea',
        ????method:?'GET',
        ????data,
        ????interceptors:?{
        ??????requestInterceptors(res)?{
        ????????console.log('接口請求攔截')

        ????????return?res
        ??????},
        ??????responseInterceptors(result)?{
        ????????console.log('接口響應(yīng)攔截')
        ????????return?result
        ??????},
        ????},
        ??})
        }
        onMounted(async?()?=>?{
        ??const?res?=?await?get15DaysWeatherByArea({
        ????apiKey:?import.meta.env.VITE_APP_KEY,
        ????area:?'北京市',
        ??})
        ??console.log(res.result.dayList)
        })
        script>

        如果在實際開發(fā)中可以將這些代碼分別抽離。

        上面的代碼在命令中輸出

        接口請求攔截
        實例請求攔截器
        全局請求攔截器
        實例響應(yīng)攔截器
        全局響應(yīng)攔截器
        接口響應(yīng)攔截
        [{…},?{…},?{…},?{…},?{…},?{…},?{…},?{…},?{…},?{…},?{…},?{…},?{…},?{…},?{…}]

        測試取消請求

        首先我們在.server/index.ts中對取消請求方法進行導(dǎo)出,實現(xiàn)代碼如下:

        //?取消請求
        export?const?cancelRequest?=?(url:?string?|?string[])?=>?{
        ??return?request.cancelRequest(url)
        }
        //?取消全部請求
        export?const?cancelAllRequest?=?()?=>?{
        ??return?request.cancelAllRequest()
        }

        然后我們在app.vue中對其進行引用,實現(xiàn)代碼如下: