layui-layer load 弹窗自动关闭的问题

本文最后更新于:2020年12月30日 下午

场景

项目中的 Ajax 加载时的 loading 框有时候会关闭了弹窗之后很久页面上的数据才加载出来,而且这个问题是随机出现的,有些页面存在,有些页面则正常。

最小复现代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/rx-util.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/layer.js"></script>
    <script>
      /**
       * 加载遮罩框
       *
       * @returns {Function} 一个关闭遮罩框的函数
       */
      function load() {
        const id = layer.load(1);
        return () => {
          layer.close(id);
        };
      }
      /**
       * 模拟 ajax 异步请求
       */
      async function request(time) {
        const close = load();
        console.log("request start: ", time);
        await rx.wait(time);
        close();
        console.log("request end: ", time);
      }

      (() => {
        request(5000).then(() => console.log("第二个请求加载完成了"));
        request(1000).then(() => console.log("第一个请求加载完成了"));
      })();
    </script>
  </body>
</html>

控制台打印

request start:  5000
request start:  1000
request end:  1000
第一个请求加载完成了
request end:  5000
第二个请求加载完成了

思考

本来吾辈猜测是 vuejs 页面渲染的锅,认为 vuejs 的生命周期函数 mouted 执行时 DOM 还没加载完全的缘故。
所以把 load 异步化,等待 document 加载完毕才会真正执行。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/rx-util.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/layer.js"></script>
    <script>
      /**
       * 加载遮罩框
       *
       * @returns {Function} 一个关闭遮罩框的函数
       */
      async function load() {
        await rx.wait(() => document.readyState === "complete");
        const id = layer.load(1);
        return async () => {
          await rx.wait(() => document.readyState === "complete");
          layer.close(id);
        };
      }
      /**
       * 模拟 ajax 异步请求
       */
      async function request(time) {
        const close = await load();
        console.log("request start: ", time);
        await rx.wait(time);
        await close();
        console.log("request end: ", time);
      }

      (() => {
        request(5000).then(() => console.log("第二个请求加载完成了"));
        request(1000).then(() => console.log("第一个请求加载完成了"));
      })();
    </script>
  </body>
</html>

控制台打印

request start:  5000
request start:  1000
request end:  1000
第一个请求加载完成了
request end:  5000
第二个请求加载完成了

然而实际上却并不是这个问题。。。

经过某位网友提醒,layer 源码中默认只允许一个活动的 load 弹窗。瞬间吾辈都不知道要怎么吐槽了,单例模式避免无谓的内存浪费是正常的,然而新的 load 函数却会关闭之前的 load 这种操作真的是很厉害了呢

例如下面这段代码,无论调用多少次 layer.close(id1),页面上的 loading 都不会关闭。。。

const id1 = layer.load();
const id2 = layer.load();
layer.close(id1);
layer.close(id1);
// ...
layer.close(id1);
layer.close(id1);

这里吾辈可以想象到,layer 认为先加载的 load() 就应该先被 close(),而没有考虑到复杂异步的情况。

解决

既然 layer 的 load 本身存在缺陷,那么却是只能自己对 loadclose 功能做控制了
基本思路

  1. layer.load 每次都会关闭掉之前的弹窗,那么就记录最后一次的弹窗 id,在真正需要关闭的时候 close 掉就好了
  2. layer.load 关闭是直接关闭弹窗,如果是最后一个就会出现弹窗消失但数据没加载完全的问题,那么关闭这儿要判断当前是否还有活动的弹窗,只有在没有的情况下才真正关闭

修改后的代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/rx-util.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/layer.js"></script>
    <script>
      /**
       * 加载遮罩框
       *
       * @returns {Function} 一个关闭遮罩框的函数
       */
      const load = ((num, lastId) => () => {
        lastId = layer.load(1);
        num++;
        return () => {
          num--;
          if (num < 0) {
            num = 0;
          }
          if (num > 0) {
            console.log("弹窗没有真正关闭哦");
            return;
          }
          layer.close(lastId);
          console.log("弹窗真的关闭啦");
        };
      })(0);

      /**
       * 模拟 ajax 异步请求
       */
      async function request(time) {
        const close = await load();
        console.log("request start: ", time);
        await rx.wait(time);
        await close();
        console.log("request end: ", time);
      }

      (() => {
        request(5000).then(() => console.log("第二个请求加载完成了"));
        request(1000).then(() => console.log("第一个请求加载完成了"));
      })();
    </script>
  </body>
</html>

控制台打印

request start:  5000
request start:  1000
弹窗没有真正关闭哦
request end:  1000
第一个请求加载完成了
弹窗真的关闭啦
request end:  5000
第二个请求加载完成了

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!