经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
如何在Vue项目中添加接口监听遮罩
来源:jb51  时间:2021/1/25 12:19:32  对本文有异议

一、业务背景

使用遮罩层来屏蔽用户的非正常操作,是前端经常使用的方式。但是在一些项目中,并没有对遮罩层进行统一管理,这就会造成如下的问题:
(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。

  1. class Mask {
  2. // 显示遮罩层
  3. appendMask(url: string): void{}
  4.  
  5. // 删除遮罩层
  6. removeMaskl(url: string): void{}
  7. }
  8.  

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

  1. // 监听对象数据类型定义
  2. interface HTTPDictInterface {
  3. [index: string]: number;
  4. }
  5.  
  6. appendMask(url: string): void{
  7.  
  8. if(!this.monitorHTTPDict[url]){
  9. this.monitorHTTPDict[url] = 0;
  10. }
  11. this.monitorHTTPDict[url] += 1;
  12.  
  13. // 如果存在监听接口,则显示遮罩层
  14. if(!this.mask && Object.keys(this.monitorHTTPDict).length){
  15.  
  16. // 在body上添加遮罩层样式,$Mask为遮罩层样式组件
  17. const Constructor = Vue.extend(Vue.prototype.$Mask);
  18. this.mask = new Constructor().$mount();
  19.  
  20. document.body.appendChild(this.mask.$el);
  21. }
  22. }
  23.  

(3)删除遮罩层函数,每次请求结束之后都会调用该函数,当发现请求监听对象为空时,删除的遮罩层。如果没有pending状态的接口,删除该对接的key。该对象为空且有遮罩层的情况下,删除遮罩层。

  1. removeMask(url: string): void{
  2.  
  3. // 成功返回后
  4. if (this.monitorHTTPDict[monitorUrl]) {
  5. this.monitorHTTPDict[monitorUrl] -= 1;
  6. if (this.monitorHTTPDict[monitorUrl] <= 0) {
  7. delete this.monitorHTTPDict[monitorUrl];
  8. }
  9. }
  10.  
  11. // hasMask用以检测页面是否存在遮罩层标签元素
  12. if (this.mask && this.hasMask() && !Object.keys(this.monitorHTTPDict).length) {
  13. document.body.removeChild(this.mask.$el);
  14. this.mask = null;
  15. }
  16.  
  17. this.timer = null;
  18. }
  19.  

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

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

  1. instance.interceptors.request.use((config) => {
  2.  
  3. // 添加遮罩层
  4. mask.appendMask(config.url);
  5.  
  6. return config;
  7. });
  8.  

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

  1. // 通过传参来决定是否使用该方法
  2.  
  3. init(){
  4. if (this.autoMonitoring){
  5. this.initRequestMonitor();
  6. }
  7. }
  8.  
  9. // 新的xmlhttprequest类型
  10. interface NewXhrInterface extends XMLHttpRequest{
  11. requestUrl?: string
  12. }
  13.  
  14. // 原生注入
  15. initRequestMonitor(): void{
  16.  
  17. let OldXHR = window.XMLHttpRequest;
  18. let maskClass: Mask = this;
  19.  
  20. // @ts-ignore,编码规范不允许修改XMLHttpRequest
  21. window.XMLHttpRequest = function () {
  22.  
  23. let realXHR: NewXhrInterface = new OldXHR();
  24. let oldOpen: Function = realXHR.open;
  25.  
  26. realXHR.open = (...args: (string | boolean | undefined | null)[]): void => {
  27.  
  28. realXHR.requestUrl = (args[1] as string);
  29. oldOpen.apply(realXHR, args);
  30.  
  31. };
  32.  
  33. realXHR.addEventListener(`loadstart`, () => {
  34.  
  35. const requestUrl: string = (realXHR.requestUrl as string);
  36.  
  37. const url: string = maskClass.cleanBaseUrl(requestUrl);
  38.  
  39. // 开启遮罩
  40. maskClass.appendMask(url);
  41. });
  42.  
  43. realXHR.addEventListener(`loadend`, () => {
  44.  
  45. const responseURL: string = (realXHR as XMLHttpRequest).responseURL;
  46. const url: string = maskClass.cleanBaseUrl(responseURL);
  47.  
  48. // 删除遮罩
  49. maskClass.removeMask(url);
  50. });
  51.  
  52. return realXHR;
  53. };
  54. }
  55.  

(3)注入使用方式,直接调用init。这样改项目的所有请求都会经过Mask。

  1. new Mask().init()

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

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

  1. // key需要监听的路由页面,value为一个数组,数组中填写的接口为黑名单,不需要监听的接口
  2. const PAGE_ONE = `/home`;
  3. const PAGE_TWO = `/login`;
  4. const HTTO_ONE = `xxx`
  5.  
  6. export const maskUrlList = {
  7. [PAGE_ONE]: [HTTO_ONE],
  8. [PAGE_TWO]: [],
  9. };
  10.  

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

  1. appendMask(url: string): void{
  2.  
  3. // 获取当前页面的path,获取页面路径,根据hash和history模式进行区分
  4. const monitorPath: string = this.getMonitorPath();
  5.  
  6. // maskUrlList为配置项,先检查页面路由,之后检查是否存在黑名单
  7. if (this.maskUrlList[monitorPath]
  8. && !this.maskUrlList[monitorPath].includes(url)) {
  9. if (this.monitorHTTPDict[url] === undefined) {
  10. this.monitorHTTPDict[url] = 0;
  11. }
  12. this.monitorHTTPDict[monitorUrl] += 1;
  13. }
  14.  
  15. // 添加遮罩层
  16. if (!this.mask && this.hasMonitorUrl()) {
  17. const Constructor = Vue.extend(Vue.prototype.$Mask);
  18. this.mask = new Constructor().$mount();
  19.  
  20. document.body.appendChild(this.mask.$el);
  21. }
  22. }
  23.  

5、细节问题

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

  1. // 清理遮罩层
  2. if (!this.timer) {
  3. this.timer = window.setTimeout(() => {
  4.  
  5. if (this.mask && this.hasMask() && !this.hasMonitorUrl()) {
  6. document.body.removeChild(this.mask.$el);
  7. this.mask = null;
  8. }
  9.  
  10. this.timer = null;
  11.  
  12. }, 0);
  13. }
  14.  

(2)过滤接口的‘?',以及hash模式下的‘#',

  1. // 获取请求接口的url
  2. getMonitorUrl(url: string): string{
  3. const urlIndex: number = url.indexOf(`?`);
  4. let monitorUrl: string = url;
  5. if (urlIndex !== -1) {
  6. monitorUrl = url.substring(0, urlIndex);
  7. }
  8. return monitorUrl;
  9. }
  10. // 获取当前路由path
  11. getMonitorPath(): string{
  12.  
  13. const path: string = this.mode === HASH_TYPE ? window.location.hash : window.location.pathname;
  14.  
  15. let monitorPath: string = path;
  16.  
  17. if (this.mode === HASH_TYPE) {
  18. monitorPath = monitorPath.substring(path.indexOf(`#`) + 1);
  19. }
  20.  
  21. // 截图路径,删除请求参数
  22. const hashIndex: number = monitorPath.indexOf(`?`);
  23.  
  24. if (hashIndex !== -1) {
  25. monitorPath = monitorPath.substring(0, hashIndex);
  26. }
  27.  
  28. return monitorPath;
  29. }
  30.  

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

  1. // 去除baseUrl
  2. cleanBaseUrl(fullUrl: string): string {
  3.  
  4. const baseUrlLength: number = this.baseUrl.length;
  5. return fullUrl.substring(baseUrlLength);
  6. }
  7.  

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

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

四、总结

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号