Vue+Axios熱更新導(dǎo)致響應(yīng)攔截器重復(fù)注冊(cè)問(wèn)題的解決方案
在使用 Vue 3(或 Vue 2)配合 Vite / Webpack 開(kāi)發(fā)時(shí),我們經(jīng)常會(huì)遇到一個(gè)“隱性陷阱”:開(kāi)發(fā)環(huán)境下熱更新(HMR, Hot Module Replacement)會(huì)導(dǎo)致 Axios 響應(yīng)攔截器被重復(fù)注冊(cè)。這個(gè)問(wèn)題在控制臺(tái)中表現(xiàn)為接口響應(yīng)日志被多次打印、錯(cuò)誤提示重復(fù)彈出,甚至引發(fā)邏輯混亂。
本文將結(jié)合實(shí)際代碼,深入剖析該問(wèn)題的成因,并介紹一種穩(wěn)定、可靠的解決方案。
一、問(wèn)題現(xiàn)象
假設(shè)我們?cè)?request.js 中配置了 Axios 的響應(yīng)攔截器:
// request.js
import axios from 'axios'
axios.interceptors.response.use(
(res) => {
console.log('響應(yīng)攔截器執(zhí)行')
// 處理響應(yīng)邏輯...
return res.data
},
(err) => {
// 錯(cuò)誤處理...
return Promise.reject(err)
}
)當(dāng)你在開(kāi)發(fā)過(guò)程中修改任意文件觸發(fā)熱更新后,再次發(fā)起請(qǐng)求,會(huì)發(fā)現(xiàn)控制臺(tái)中 "響應(yīng)攔截器執(zhí)行" 被打印了 兩次、四次甚至更多次!
這說(shuō)明:每次熱更新都會(huì)重新執(zhí)行 request.js 模塊,從而重復(fù)注冊(cè)新的攔截器。而舊的攔截器并未被清除,導(dǎo)致多個(gè)攔截器實(shí)例同時(shí)生效。
二、錯(cuò)誤嘗試:使用局部變量控制
很多開(kāi)發(fā)者第一反應(yīng)是用一個(gè)布爾變量來(lái)防止重復(fù)注冊(cè):
let isResponseRegistered = false
if (!isResponseRegistered) {
axios.interceptors.response.use(...)
isResponseRegistered = true
}但你會(huì)發(fā)現(xiàn),這個(gè)方案在熱更新下依然失效——控制臺(tái)還是打印了兩次日志。
為什么?
因?yàn)?熱更新會(huì)重新執(zhí)行整個(gè)模塊(包括變量聲明)。也就是說(shuō),每次 HMR 觸發(fā)時(shí),isResponseRegistered 都會(huì)被重置為 false,于是攔截器又被注冊(cè)了一次。
關(guān)鍵點(diǎn):模塊級(jí)變量在熱更新時(shí)會(huì)被重新初始化,無(wú)法跨更新周期保持狀態(tài)。
三、正確方案:將標(biāo)志掛載到 Axios 實(shí)例上
要解決這個(gè)問(wèn)題,必須使用一個(gè) 不會(huì)被熱更新重置的對(duì)象屬性 來(lái)記錄是否已注冊(cè)攔截器。
Axios 本身是一個(gè)對(duì)象(函數(shù)對(duì)象),我們可以直接在其上掛載自定義屬性:
// 防止熱更新重復(fù)注冊(cè)響應(yīng)攔截器
if (!axios.__myResponseInterceptor__) {
axios.interceptors.response.use(
(res) => {
console.log('響應(yīng)攔截器執(zhí)行')
// ...處理邏輯
return res.data
},
(err) => {
return Promise.reject(err)
}
)
axios.__myResponseInterceptor__ = true // 標(biāo)記已注冊(cè)
}為什么這個(gè)方案有效?
axios是從node_modules引入的模塊,在 HMR 中 通常不會(huì)被重新加載(屬于“穩(wěn)定的依賴”)。- 因此,掛載在
axios上的屬性__myResponseInterceptor__在熱更新期間保持不變。 - 即使
request.js被反復(fù)執(zhí)行,也能準(zhǔn)確判斷攔截器是否已存在。
這是一種被社區(qū)廣泛驗(yàn)證的有效做法,類(lèi)似方案也用于防止重復(fù)注冊(cè)全局組件、插件等。
四、完整代碼示例(來(lái)自實(shí)際項(xiàng)目)
以下是從你提供的 request.js 中提取的核心邏輯:
// 防止響應(yīng)攔截器被重復(fù)注冊(cè)
if (!axios.__myResponseInterceptor__) {
axios.interceptors.response.use(
(res) => {
// 二進(jìn)制數(shù)據(jù)直接返回
if (res.request?.responseType === 'blob' || ...) {
return res.data
}
if (!res.data) {
return { code: 200, data: null, message: '熱更新中' }
}
switch (Number(res.data.code)) {
case 401:
router.push('/login')
break
case 200:
if (res.data.token) {
localStorage.setItem('token', res.data.token)
}
return res.data
default:
return res.data
}
},
(err) => {
return Promise.reject(err)
}
)
axios.__myResponseInterceptor__ = true // 關(guān)鍵:標(biāo)記已注冊(cè)
}該方案成功解決了熱更新下的重復(fù)注冊(cè)問(wèn)題,且不影響生產(chǎn)環(huán)境(生產(chǎn)環(huán)境無(wú) HMR,只執(zhí)行一次)。
五、其他可行方案(補(bǔ)充)
使用 import.meta.hot?.accept() 手動(dòng)管理副作用(Vite 特有)
可在模塊卸載時(shí)移除攔截器,但實(shí)現(xiàn)復(fù)雜,不推薦。
將攔截器注冊(cè)移到 main.js 或入口文件
減少被 HMR 影響的概率,但若入口文件也被更新,仍可能失效。
使用單例模式封裝 Axios 實(shí)例
例如導(dǎo)出一個(gè) createAxiosInstance() 函數(shù),并緩存實(shí)例。但需確保調(diào)用方不重復(fù)創(chuàng)建。
相比之下,掛載標(biāo)志位到 axios 對(duì)象上是最簡(jiǎn)單、可靠、低侵入性的方案。
六、總結(jié)
| 方案 | 是否有效 | 原因 |
|---|---|---|
| 局部變量 let flag = false | ? | 熱更新重置變量 |
| 掛載到 axios 對(duì)象上 | ? | axios 模塊穩(wěn)定,屬性持久 |
| 移到 main.js | ?? | 降低概率,但非根治 |
| 手動(dòng) HMR 清理 | ? 但復(fù)雜 | 需要監(jiān)聽(tīng)模塊更新事件 |
最佳實(shí)踐:在開(kāi)發(fā) Axios 封裝庫(kù)時(shí),務(wù)必考慮 HMR 場(chǎng)景,使用 axios.__xxx__ 標(biāo)志位防止重復(fù)注冊(cè)攔截器。
希望本文能幫助你徹底理解并解決這一“開(kāi)發(fā)期幽靈 bug”。
到此這篇關(guān)于Vue+Axios熱更新導(dǎo)致響應(yīng)攔截器重復(fù)注冊(cè)問(wèn)題的解決方案的文章就介紹到這了,更多相關(guān)Vue Axios熱更新導(dǎo)致攔截器重復(fù)注冊(cè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Vue項(xiàng)目中引入JQuery-ui插件的講解
今天小編就為大家分享一篇關(guān)于在Vue項(xiàng)目中引入JQuery-ui插件的講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
element?tab標(biāo)簽管理路由頁(yè)面的項(xiàng)目實(shí)踐
本文主要介紹了element?tab標(biāo)簽管理路由頁(yè)面的項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Vue Promise解決回調(diào)地獄問(wèn)題實(shí)現(xiàn)方法
這篇文章主要介紹了Vue Promise解決回調(diào)地獄問(wèn)題,總的來(lái)說(shuō)這并不是一道難題,那為什么要拿出這道題介紹?拿出這道題真正想要傳達(dá)的是解題的思路,以及不斷優(yōu)化探尋最優(yōu)解的過(guò)程。希望通過(guò)這道題能給你帶來(lái)一種解題優(yōu)化的思路2023-01-01
Vue項(xiàng)目中接口調(diào)用的詳細(xì)講解
應(yīng)公司需求,接口需要對(duì)接vue,記錄一下碰到的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目中接口調(diào)用的詳細(xì)講解,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
淺談vue中$event理解和框架中在包含默認(rèn)值外傳參
這篇文章主要介紹了淺談vue中$event理解和框架中在包含默認(rèn)值外傳參,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Element-ui Drawer抽屜按需引入基礎(chǔ)使用
這篇文章主要為大家介紹了Element-ui Drawer抽屜按需引入基礎(chǔ)使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

