如何在Vue项目中添加接口监听遮罩

一、业务背景

使用遮罩层来屏蔽用户的非正常操作,是前端经常使用的方式。但是在一些项目中,并没有对遮罩层进行统一管理,这就会造成如下的问题:
(1)所有的业务组件都要引入遮罩层组件,也就是每个.vue业务组件,都在template中引入了Mask组件。组件在项目的各个角落都存在,不利于管理,代码极度冗余。
(2)Mask组件都分散到业务的各个角落,所以控制是否显示遮罩层的变量也散在业务组件中。比如使用maskShow来控制是否展示遮罩层时,一个较为复杂的项目中会产生200+的maskShow变量。
(3)maskShow过多且融入在业务中,同时maskShow的变量往往写在接口的回调函数中,经常会出现忘记改变变量的情况,造成遮罩层该显示和不该显示的逻辑出错。
(4)项目经常是在本地调试,而真实运行却又在线上,(3)中的问题在本地经常无法验证出。因为这些问题经常是在线上网络环境较差的情况出现。如一个按钮按完之后,需要等接口返回才能再次点击,但是本地因为返回速度较快,如果忘记添加遮罩层也不会有什么问题。但如果是网络有问题的线上环境,就很容易出现,且该问题一旦出现,很难定位,大大影响工作效率。

二、问题分析

根据上述的背景,在实际项目中添加一个公共的遮罩层组件进行管理,就变的十分有意义。经过分析,具体需要解决如下问题:
(1)遮罩层出现和关闭的时机。
(2)Mask组件设计。
(3)该组件如何优雅的引入到项目中,不产生耦合。
(4)如何在已有的项目中,渐进式的更换原有的maskShow的方式,从而不造成大面积问题。
(5)细节问题

三、组件设计

1、遮罩层出现和关闭的时机

该问题根据不同业务需求决定,但是笔者认为,大部分遮罩的出现和关闭主要取决于接口的请求和返回,一个接口在请求pending状态下,显示遮罩层,所有接口返回则关闭遮罩。本文主要解决的是接口请求遮罩问题,使用ts进行编写,且并不会罗列所有细节。

2、Mask组件设计

Mask组件为一个class,将细节屏蔽在class内部。
(1)class内部最主要功能为添加和删除遮罩层,传输的当前请求接口的url。

class Mask {
 // 显示遮罩层
 appendMask(url: string): void{}

 // 删除遮罩层
 removeMaskl(url: string): void{}
}

(2)添加遮罩层函数,请求时调用该函数,传入当前接口url。函数内部维护一个监听对象,用以监听当前是否存在pending状态的请求。该对象的value为该接口pending状态的数量。通过假设遮罩视图组件已经挂载到了Vue原型链上,如果没有,则在组件上方引入即可。

// 监听对象数据类型定义
interface HTTPDictInterface {
 [index: string]: number;
}

appendMask(url: string): void{ 

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

 // 如果存在监听接口,则显示遮罩层
 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)删除遮罩层函数,每次请求结束之后都会调用该函数,当发现请求监听对象为空时,删除的遮罩层。如果没有pending状态的接口,删除该对接的key。该对象为空且有遮罩层的情况下,删除遮罩层。

removeMask(url: string): void{

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

 // hasMask用以检测页面是否存在遮罩层标签元素
 if (this.mask && this.hasMask() && !Object.keys(this.monitorHTTPDict).length) {
 document.body.removeChild(this.mask.$el);
 this.mask = null;
 }

 this.timer = null;
}

3、该组件如何优雅的引入到项目中,不产生耦合。

使用该组件,需要在所有的请求发起之前调用appendMask函数,所有的请求结束之后调用removeMask函数。这就有如下两种调用方式。
(1)使用axios等组件的回调,完成函数调用。但是这种做法并没有将Mask组件的代码独立于项目,它依赖于具体接口框架的API。

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

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

 return config;
});

(2)添加init函数,直接在原生XMLHttpRequest对象中注入回调。更改原生XMLHttpRequest函数,在事件'loadstart'和'loadend'中注入回调,需要注意的是,loadstart接收的传参中,并没有当前请求的url,所以还需要改写open函数,把open接收传参的url挂载到新的xhr对象上。慎用该方法。因为更改原生API的方式十分危险,在很多编码规范中是禁止的,如果所有人都对原生API进行改写,当同时引入这些框架会产生冲突,造成无法意料的后果。

// 通过传参来决定是否使用该方法

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

// 新的xmlhttprequest类型
interface NewXhrInterface extends XMLHttpRequest{
 requestUrl?: string
}

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

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

 // @ts-ignore,编码规范不允许修改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);

 // 开启遮罩
 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)注入使用方式,直接调用init。这样改项目的所有请求都会经过Mask。

new Mask().init()

4、如何在已有的项目中,渐进式的更换原有的maskShow的方式,从而不造成大面积问题。

如果直接在全项目中使用,牵扯的面积就会变得很广,会大面积的产生问题,反而得不偿失。所以应该采取一种渐进更换的方式,做到平滑过渡。主要思路是通过配置页面和黑名单的方式,来决定哪些页面引入该组件,从而让每个组员自己修改,毕竟页面的负责人才是最了解当前页面业务的人。至于如何黑名单还是白名单,则由项目的具体业务决定。

// key需要监听的路由页面,value为一个数组,数组中填写的接口为黑名单,不需要监听的接口
const PAGE_ONE = `/home`;
const PAGE_TWO = `/login`;
const HTTO_ONE = `xxx`

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

appendMask方法过滤黑名单和没有配置的页面。maskUrlList为控制的对象,先检查页面路由,之后检查是否存在黑名单。

appendMask(url: string): void{

 // 获取当前页面的path,获取页面路径,根据hash和history模式进行区分
 const monitorPath: string = this.getMonitorPath();

 // maskUrlList为配置项,先检查页面路由,之后检查是否存在黑名单
 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、细节问题

(1)渲染之后才关闭遮罩层,将实际删除遮罩层逻辑放到定时器中,Vue的异步渲染采用的promise,所以关闭在如果放在渲染之后,需要放入setTimeout中。这里涉及到事件循环的知识。当接口返回,如果需要渲染页面,则会异步执行一个Promise,Promise为微任务,setTimeout为宏任务,当主线程执行完毕后,会先执行微任务,之后才会执行异步的宏任务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)过滤接口的‘?',以及hash模式下的‘#',

// 获取请求接口的url
getMonitorUrl(url: string): string{
 const urlIndex: number = url.indexOf(`?`);
 let monitorUrl: string = url;
 if (urlIndex !== -1) {
 monitorUrl = url.substring(0, urlIndex);
 }
 return monitorUrl;
}
// 获取当前路由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);
 }

 // 截图路径,删除请求参数
 const hashIndex: number = monitorPath.indexOf(`?`);

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

 return monitorPath;
}

(3)接口过滤baseUrl。细心的话,会发现在使用axios的接口时,自行决定是否带入baseUrl,那是因为axios会在请求时进行区分过滤。如果项目前期并没有很好的定义使用方式的话,会有两种不同使用axios的方式。那么,就需要对baseUrl进行过滤。

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

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

}

(4)组件初始化,通过传入params的方式,将对象实例化出来。

new Mask({
 modeType, // hash或history
 autoMonitoring, // 是否更写原生XMLHttpRequest对象
 maskUrlList, // 配置引入的页面和接口
 baseUrl, // 当前项目的baseUrl
 ...
}).init()

四、总结

本文介绍了统一遮罩层的背景、问题及设计方案。但并没有将所有细节进行列举,这需要根据实际业务进行选择。但大体方案已经列出:
(1)遮罩层应该在一些接口pending装的时候显示,所有接口返回后自动关闭。这里的接口是指需要监听的接口
(2)组件最重要的两个函数为appendMask添加遮罩层和removeMask删除遮罩层。
(3)如果想Mask完全独立,并不想依赖于第三方库(axios)的回调,可以直接对XMLHttpRequest进行改写,但这样做风险很大,并不建议。
(4)组件更换统一组员自己配置路由及监听接口的方式。这里的逻辑可以自行决定,如果要监听的接口多,可以采用黑名单,反之则白名单。
(5)对渲染的优化、请求带参数、路由的模式进行了优化。

到此这篇关于如何在Vue项目中添加接口监听遮罩的文章就介绍到这了,更多相关Vue 接口监听遮罩内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue使用Proxy监听所有接口状态的方法实现

    在开发项目过程中几乎所有接口都需要知道它的返回状态,比如失败或者成功,在移动端通常后台会返回结果,而我们只需要一个弹窗来弹出来结果就可以了.但是这个弹窗如果在整个项目里需要手动去每一个都定义,那是非常庞大的代码量,而且维护起来非常的麻烦.通常做法就是绑定在原型上一个公共方法,比如this.message('后台返回接口信息'). 这样看似省力了很多其实还是很麻烦. 如果使用了proxy做一个全局代理,那么就完全不一样了.不管任何一个api都会将状态传递个这个代理中心,并且由代理中心直接反应结果.

  • 如何在Vue项目中添加接口监听遮罩

    一.业务背景 使用遮罩层来屏蔽用户的非正常操作,是前端经常使用的方式.但是在一些项目中,并没有对遮罩层进行统一管理,这就会造成如下的问题: (1)所有的业务组件都要引入遮罩层组件,也就是每个.vue业务组件,都在template中引入了Mask组件.组件在项目的各个角落都存在,不利于管理,代码极度冗余. (2)Mask组件都分散到业务的各个角落,所以控制是否显示遮罩层的变量也散在业务组件中.比如使用maskShow来控制是否展示遮罩层时,一个较为复杂的项目中会产生200+的maskShow变量.

  • 如何在Vue项目中使用axios请求

    在实际的项目中,和后台的数据交互是少不了的,我通常使用的是 axios 库,所以以下示例也是以 axios 为基础来进行封装的. 1.安装 首先是 npm 安装 axios 很简单:npm install axios 2.没有封装存在的问题 如果在没有封装接口的项目中,在文件中随处可以看到如下的接口调用方法: this.$axios.post("/user/add", { params: { name: this.name, age: this.age } }) .then(res =

  • 如何在vue项目中使用百度地图API

    目录 1.在百度地图开放平台注册账号并登录 2.选择自己所需的地图版本: 3.在我们的vue项目中的public文件夹下的index.html中引入并记得替换上你的ak(这个ak不是其他的ak哈) 4.之后就可以随处使用我们的百度地图了: 5.标记点的添加并拥有点击弹窗效果: 6.为坐标点添加文本标记: 1.在百度地图开放平台注册账号并登录 网址:http://lbsyun.baidu.com/index.php?title=jspopularGL 2.选择自己所需的地图版本: 我个人使用2.0

  • 手把手教你如何在vue项目中使用rem布局

    目录 如何在vue项目中使用rem布局 方法一:使用lib-flexible 1. 安装包 2. 引入文件 3. 根据需要设置rem 4.使用rem 方法二:使用postcss-pxtorem 1. 安装包 2. 创建rem.js文件 3. 新建 .postcssrc.js 4. 在main.js中引入 5. 设置body的font-size 6. 可参照流程图 总结 如何在vue项目中使用rem布局 场景: 在我们开发移动端项目中,实现手机页面布局,需要使用rem布局,那么该如何使用呢? 本文

  • 详解如何在Vue项目中导出Excel

    Excel 导出 Excel 的导入导出都是依赖于js-xlsx来实现的. 在 js-xlsx的基础上又封装了Export2Excel.js来方便导出数据. 使用 由于 Export2Excel不仅依赖js-xlsx还依赖file-saver和script-loader. 所以你先需要安装如下命令: npm install xlsx file-saver -S npm install script-loader -S -D 由于js-xlsx体积还是很大的,导出功能也不是一个非常常用的功能,所以

  • 如何在Vue项目中应用TypeScript类

    目录 一.前言 二.使用 1.@Component 2.compued.data.methods 3.@props 4.@watch 5.@emit 三 .总结 一.前言 TypeScript是基于vue-class-component库而来,这个库vue官方推出的一个支持使用class方式来开发vue单文件组件的库 主要的功能如下: methods 可以直接声明为类的成员方法 计算属性可以被声明为类的属性访问器 初始化的 data 可以被声明为类属性 data.render 以及所有的 Vue

  • vue项目中添加electron的详细代码

    1.在package.json中添加 "main": "electron.js", 在 "scripts": {添加: "package": "electron-packager ./ appName --overwrite" 在"dependencies": {添加: "electron-share-memory": "^1.0.1", &quo

  • 如何在vue项目中使用UEditor--plus

    目录 1:UEditor-plus富文本编辑器如何在vue项目中使用 2.使用方法 3.运行项目 1:UEditor-plus富文本编辑器如何在vue项目中使用 备注:UEditor是由百度web前端研发部开发的所见即所得的开源富文本编辑器,由于该项目不在维护:程序员自发对其进行了维护,详见 https://gitee.com/modstart-lib/ueditor-plus?_from=gitee_search: 2.使用方法 第一步:在git上拉取ueditor代码到本地 第二步:解压后找

  • vue项目中api接口管理总结

    默认vue项目中已经使用vue-cli生成,安装axios,基于element-ui开发,axiosconfig目录和api目录是同级,主要记录配置的相关. 1. 在axiosconfig目录下的axiosConfig.js import Vue from 'vue' import axios from 'axios' import qs from 'qs' import { Message, Loading } from 'element-ui' // 响应时间 axios.defaults.

  • 详解如何在vue项目中使用layui框架及采坑

    根据官网的文档,要在一个html文件下使用layui里面的组件库其实很简单,但是在vue项目中使用该ui库却存在着很多坑,下面我们就详细讲解一下如何在vue-cli搭建的项目下使用layui 1.第一个坑:vue项目中使用npm安装引入时报错(目前并没有找到引入的合适的方式,知道的可以留言探讨) 在官网中我们可以看到他提到支持npm安装,但是当我们安装成功后,在main.js中引入时却报并不能找到该模块的错. 所以,我们先下载文件包,然后在html文件中用link和script标签的方式引入 2

随机推荐