溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

怎么在Vue項(xiàng)目中添加一個(gè)接口監(jiān)聽(tīng)遮罩

發(fā)布時(shí)間:2021-01-25 14:59:22 來(lái)源:億速云 閱讀:297 作者:Leah 欄目:開(kāi)發(fā)技術(shù)

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)怎么在Vue項(xiàng)目中添加一個(gè)接口監(jiān)聽(tīng)遮罩,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

一、業(yè)務(wù)背景

使用遮罩層來(lái)屏蔽用戶(hù)的非正常操作,是前端經(jīng)常使用的方式。但是在一些項(xiàng)目中,并沒(méi)有對(duì)遮罩層進(jìn)行統(tǒng)一管理,這就會(huì)造成如下的問(wèn)題:
(1)所有的業(yè)務(wù)組件都要引入遮罩層組件,也就是每個(gè).vue業(yè)務(wù)組件,都在template中引入了Mask組件。組件在項(xiàng)目的各個(gè)角落都存在,不利于管理,代碼極度冗余。
(2)Mask組件都分散到業(yè)務(wù)的各個(gè)角落,所以控制是否顯示遮罩層的變量也散在業(yè)務(wù)組件中。比如使用maskShow來(lái)控制是否展示遮罩層時(shí),一個(gè)較為復(fù)雜的項(xiàng)目中會(huì)產(chǎn)生200+的maskShow變量。
(3)maskShow過(guò)多且融入在業(yè)務(wù)中,同時(shí)maskShow的變量往往寫(xiě)在接口的回調(diào)函數(shù)中,經(jīng)常會(huì)出現(xiàn)忘記改變變量的情況,造成遮罩層該顯示和不該顯示的邏輯出錯(cuò)。
(4)項(xiàng)目經(jīng)常是在本地調(diào)試,而真實(shí)運(yùn)行卻又在線(xiàn)上,(3)中的問(wèn)題在本地經(jīng)常無(wú)法驗(yàn)證出。因?yàn)檫@些問(wèn)題經(jīng)常是在線(xiàn)上網(wǎng)絡(luò)環(huán)境較差的情況出現(xiàn)。如一個(gè)按鈕按完之后,需要等接口返回才能再次點(diǎn)擊,但是本地因?yàn)榉祷厮俣容^快,如果忘記添加遮罩層也不會(huì)有什么問(wèn)題。但如果是網(wǎng)絡(luò)有問(wèn)題的線(xiàn)上環(huán)境,就很容易出現(xiàn),且該問(wèn)題一旦出現(xiàn),很難定位,大大影響工作效率。

二、問(wèn)題分析

根據(jù)上述的背景,在實(shí)際項(xiàng)目中添加一個(gè)公共的遮罩層組件進(jìn)行管理,就變的十分有意義。經(jīng)過(guò)分析,具體需要解決如下問(wèn)題:
(1)遮罩層出現(xiàn)和關(guān)閉的時(shí)機(jī)。
(2)Mask組件設(shè)計(jì)。
(3)該組件如何優(yōu)雅的引入到項(xiàng)目中,不產(chǎn)生耦合。
(4)如何在已有的項(xiàng)目中,漸進(jìn)式的更換原有的maskShow的方式,從而不造成大面積問(wèn)題。
(5)細(xì)節(jié)問(wèn)題

三、組件設(shè)計(jì)

1、遮罩層出現(xiàn)和關(guān)閉的時(shí)機(jī)

該問(wèn)題根據(jù)不同業(yè)務(wù)需求決定,但是筆者認(rèn)為,大部分遮罩的出現(xiàn)和關(guān)閉主要取決于接口的請(qǐng)求和返回,一個(gè)接口在請(qǐng)求pending狀態(tài)下,顯示遮罩層,所有接口返回則關(guān)閉遮罩。本文主要解決的是接口請(qǐng)求遮罩問(wèn)題,使用ts進(jìn)行編寫(xiě),且并不會(huì)羅列所有細(xì)節(jié)。

2、Mask組件設(shè)計(jì)

Mask組件為一個(gè)class,將細(xì)節(jié)屏蔽在class內(nèi)部。
(1)class內(nèi)部最主要功能為添加和刪除遮罩層,傳輸?shù)漠?dāng)前請(qǐng)求接口的url。

class Mask {
 // 顯示遮罩層
 appendMask(url: string): void{}

 // 刪除遮罩層
 removeMaskl(url: string): void{}
}

(2)添加遮罩層函數(shù),請(qǐng)求時(shí)調(diào)用該函數(shù),傳入當(dāng)前接口url。函數(shù)內(nèi)部維護(hù)一個(gè)監(jiān)聽(tīng)對(duì)象,用以監(jiān)聽(tīng)當(dāng)前是否存在pending狀態(tài)的請(qǐng)求。該對(duì)象的value為該接口pending狀態(tài)的數(shù)量。通過(guò)假設(shè)遮罩視圖組件已經(jīng)掛載到了Vue原型鏈上,如果沒(méi)有,則在組件上方引入即可。

// 監(jiān)聽(tīng)對(duì)象數(shù)據(jù)類(lèi)型定義
interface HTTPDictInterface {
 [index: string]: number;
}

appendMask(url: string): void{ 

 if(!this.monitorHTTPDict[url]){
 this.monitorHTTPDict[url] = 0;
 }
 this.monitorHTTPDict[url] += 1;

 // 如果存在監(jiān)聽(tīng)接口,則顯示遮罩層
 if(!this.mask && Object.keys(this.monitorHTTPDict).length){

 // 在body上添加遮罩層樣式,$Mask為遮罩層樣式組件
 const Constructor = Vue.extend(Vue.prototype.$Mask);
 this.mask = new Constructor().$mount();

 document.body.appendChild(this.mask.$el);
 }
}

(3)刪除遮罩層函數(shù),每次請(qǐng)求結(jié)束之后都會(huì)調(diào)用該函數(shù),當(dāng)發(fā)現(xiàn)請(qǐng)求監(jiān)聽(tīng)對(duì)象為空時(shí),刪除的遮罩層。如果沒(méi)有pending狀態(tài)的接口,刪除該對(duì)接的key。該對(duì)象為空且有遮罩層的情況下,刪除遮罩層。

removeMask(url: string): void{

 // 成功返回后
 if (this.monitorHTTPDict[monitorUrl]) {
 this.monitorHTTPDict[monitorUrl] -= 1;
 if (this.monitorHTTPDict[monitorUrl] <= 0) {
 delete this.monitorHTTPDict[monitorUrl];
 }
 }

 // hasMask用以檢測(cè)頁(yè)面是否存在遮罩層標(biāo)簽元素
 if (this.mask && this.hasMask() && !Object.keys(this.monitorHTTPDict).length) {
 document.body.removeChild(this.mask.$el);
 this.mask = null;
 }

 this.timer = null;
}

3、該組件如何優(yōu)雅的引入到項(xiàng)目中,不產(chǎn)生耦合。

使用該組件,需要在所有的請(qǐng)求發(fā)起之前調(diào)用appendMask函數(shù),所有的請(qǐng)求結(jié)束之后調(diào)用removeMask函數(shù)。這就有如下兩種調(diào)用方式。
(1)使用axios等組件的回調(diào),完成函數(shù)調(diào)用。但是這種做法并沒(méi)有將Mask組件的代碼獨(dú)立于項(xiàng)目,它依賴(lài)于具體接口框架的API。

instance.interceptors.request.use((config) => {

 // 添加遮罩層
 mask.appendMask(config.url);

 return config;
});

(2)添加init函數(shù),直接在原生XMLHttpRequest對(duì)象中注入回調(diào)。更改原生XMLHttpRequest函數(shù),在事件'loadstart'和'loadend'中注入回調(diào),需要注意的是,loadstart接收的傳參中,并沒(méi)有當(dāng)前請(qǐng)求的url,所以還需要改寫(xiě)open函數(shù),把open接收傳參的url掛載到新的xhr對(duì)象上。慎用該方法。因?yàn)楦脑鶤PI的方式十分危險(xiǎn),在很多編碼規(guī)范中是禁止的,如果所有人都對(duì)原生API進(jìn)行改寫(xiě),當(dāng)同時(shí)引入這些框架會(huì)產(chǎn)生沖突,造成無(wú)法意料的后果。

// 通過(guò)傳參來(lái)決定是否使用該方法

init(){
 if (this.autoMonitoring){
 this.initRequestMonitor();
 }
}

// 新的xmlhttprequest類(lèi)型
interface NewXhrInterface extends XMLHttpRequest{
 requestUrl?: string
}

// 原生注入
initRequestMonitor(): void{

 let OldXHR = window.XMLHttpRequest;
 let maskClass: Mask = this;

 // @ts-ignore,編碼規(guī)范不允許修改XMLHttpRequest
 window.XMLHttpRequest = function () {

 let realXHR: NewXhrInterface = new OldXHR();
 let oldOpen: Function = realXHR.open;

 realXHR.open = (...args: (string | boolean | undefined | null)[]): void => {

 realXHR.requestUrl = (args[1] as string);
 oldOpen.apply(realXHR, args);

 };

 realXHR.addEventListener(`loadstart`, () => {

 const requestUrl: string = (realXHR.requestUrl as string);

 const url: string = maskClass.cleanBaseUrl(requestUrl);

 // 開(kāi)啟遮罩
 maskClass.appendMask(url);
 });

 realXHR.addEventListener(`loadend`, () => {

 const responseURL: string = (realXHR as XMLHttpRequest).responseURL;
 const url: string = maskClass.cleanBaseUrl(responseURL);

 // 刪除遮罩
 maskClass.removeMask(url);
 });

 return realXHR;
 };
}

(3)注入使用方式,直接調(diào)用init。這樣改項(xiàng)目的所有請(qǐng)求都會(huì)經(jīng)過(guò)Mask。

new Mask().init()

4、如何在已有的項(xiàng)目中,漸進(jìn)式的更換原有的maskShow的方式,從而不造成大面積問(wèn)題。

如果直接在全項(xiàng)目中使用,牽扯的面積就會(huì)變得很廣,會(huì)大面積的產(chǎn)生問(wèn)題,反而得不償失。所以應(yīng)該采取一種漸進(jìn)更換的方式,做到平滑過(guò)渡。主要思路是通過(guò)配置頁(yè)面和黑名單的方式,來(lái)決定哪些頁(yè)面引入該組件,從而讓每個(gè)組員自己修改,畢竟頁(yè)面的負(fù)責(zé)人才是最了解當(dāng)前頁(yè)面業(yè)務(wù)的人。至于如何黑名單還是白名單,則由項(xiàng)目的具體業(yè)務(wù)決定。

// key需要監(jiān)聽(tīng)的路由頁(yè)面,value為一個(gè)數(shù)組,數(shù)組中填寫(xiě)的接口為黑名單,不需要監(jiān)聽(tīng)的接口
const PAGE_ONE = `/home`;
const PAGE_TWO = `/login`;
const HTTO_ONE = `xxx`

export const maskUrlList = {
 [PAGE_ONE]: [HTTO_ONE],
 [PAGE_TWO]: [],
};

appendMask方法過(guò)濾黑名單和沒(méi)有配置的頁(yè)面。maskUrlList為控制的對(duì)象,先檢查頁(yè)面路由,之后檢查是否存在黑名單。

appendMask(url: string): void{

 // 獲取當(dāng)前頁(yè)面的path,獲取頁(yè)面路徑,根據(jù)hash和history模式進(jìn)行區(qū)分
 const monitorPath: string = this.getMonitorPath();

 // maskUrlList為配置項(xiàng),先檢查頁(yè)面路由,之后檢查是否存在黑名單
 if (this.maskUrlList[monitorPath]
 && !this.maskUrlList[monitorPath].includes(url)) {
 if (this.monitorHTTPDict[url] === undefined) {
 this.monitorHTTPDict[url] = 0;
 }
 this.monitorHTTPDict[monitorUrl] += 1;
 }

 // 添加遮罩層
 if (!this.mask && this.hasMonitorUrl()) {
 const Constructor = Vue.extend(Vue.prototype.$Mask);
 this.mask = new Constructor().$mount();

 document.body.appendChild(this.mask.$el);
 }
}

5、細(xì)節(jié)問(wèn)題

(1)渲染之后才關(guān)閉遮罩層,將實(shí)際刪除遮罩層邏輯放到定時(shí)器中,Vue的異步渲染采用的promise,所以關(guān)閉在如果放在渲染之后,需要放入setTimeout中。這里涉及到事件循環(huán)的知識(shí)。當(dāng)接口返回,如果需要渲染頁(yè)面,則會(huì)異步執(zhí)行一個(gè)Promise,Promise為微任務(wù),setTimeout為宏任務(wù),當(dāng)主線(xiàn)程執(zhí)行完畢后,會(huì)先執(zhí)行微任務(wù),之后才會(huì)執(zhí)行異步的宏任務(wù)setTimeout。

// 清理遮罩層
if (!this.timer) {
 this.timer = window.setTimeout(() => {

 if (this.mask && this.hasMask() && !this.hasMonitorUrl()) {
 document.body.removeChild(this.mask.$el);
 this.mask = null;
 }

 this.timer = null;

 }, 0);
}

(2)過(guò)濾接口的‘?',以及hash模式下的‘#',

// 獲取請(qǐng)求接口的url
getMonitorUrl(url: string): string{
 const urlIndex: number = url.indexOf(`?`);
 let monitorUrl: string = url;
 if (urlIndex !== -1) {
 monitorUrl = url.substring(0, urlIndex);
 }
 return monitorUrl;
}
// 獲取當(dāng)前路由path
getMonitorPath(): string{

 const path: string = this.mode === HASH_TYPE ? window.location.hash : window.location.pathname;

 let monitorPath: string = path;

 if (this.mode === HASH_TYPE) {
 monitorPath = monitorPath.substring(path.indexOf(`#`) + 1);
 }

 // 截圖路徑,刪除請(qǐng)求參數(shù)
 const hashIndex: number = monitorPath.indexOf(`?`);

 if (hashIndex !== -1) {
 monitorPath = monitorPath.substring(0, hashIndex);
 }

 return monitorPath;
}

(3)接口過(guò)濾baseUrl。細(xì)心的話(huà),會(huì)發(fā)現(xiàn)在使用axios的接口時(shí),自行決定是否帶入baseUrl,那是因?yàn)閍xios會(huì)在請(qǐng)求時(shí)進(jìn)行區(qū)分過(guò)濾。如果項(xiàng)目前期并沒(méi)有很好的定義使用方式的話(huà),會(huì)有兩種不同使用axios的方式。那么,就需要對(duì)baseUrl進(jìn)行過(guò)濾。

// 去除baseUrl
cleanBaseUrl(fullUrl: string): string {

 const baseUrlLength: number = this.baseUrl.length;
 return fullUrl.substring(baseUrlLength);
 
}

(4)組件初始化,通過(guò)傳入params的方式,將對(duì)象實(shí)例化出來(lái)。

new Mask({
 modeType, // hash或history
 autoMonitoring, // 是否更寫(xiě)原生XMLHttpRequest對(duì)象
 maskUrlList, // 配置引入的頁(yè)面和接口
 baseUrl, // 當(dāng)前項(xiàng)目的baseUrl
 ...
}).init()

上述就是小編為大家分享的怎么在Vue項(xiàng)目中添加一個(gè)接口監(jiān)聽(tīng)遮罩了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI