zoukankan      html  css  js  c++  java
  • vant 1.6X 版本 Toast 单例问题

    一、背景

    在项目中使用 toast 发现不是 vant 官方所说的默认单例模式


    1、操作

    在 created 中多次调用 Toast.loading() 发现生成了多个 toast 元素
    
    使用 Toast.clear() 只能关掉一个
    

    2、项目中对 Toast 封装

    Toast.loading 以及 Toast.clear 封装

    Vue.prototype.$loading = msg => {
        Toast.loading({
            mask: true,
            message: msg || langMap[lang]['loading'],
            duration: 0,
            position: 'center'
        })
    }
    Vue.prototype.$close = () => {
        Toast.clear()
    }
    

    3、多次调用

    created() {
        // 第一次
        // 此处 loading 可能是其他方法调用的
        // 这里用来制造问题出现的场景
        this.$loading()
    
        this.init()
    },
    methods: {
        init() {
            try {
                // 第二次
                this.$loading()
            } catch (e) {
                console.log(e)
            } finally {
                this.$close()
            }
        }
    }
    

    4、结果

    浏览器页面创建了多个 Toast 元素


    二、查看源码实现

    1、Toast.loading

    1.1 根据注册代码,可以看出,Toast.loading 实际还是调用了 Toast 本身

    var createMethod = function createMethod(type) {
      return function (options) {
        return Toast(_extends({
          type: type
        }, parseOptions(options)));
      };
    };
    // 注册 loading 方法
    ['loading', 'success', 'fail'].forEach(function (method) {
      Toast[method] = createMethod(method);
    });
    

    1.2 查看 Toast 函数实现

    可以看到主要是以 createInstance 来实现创建 toast

    function Toast(options) {
        // ...
        // 忽略以上逻辑
    
        var toast = createInstance(); // should add z-index if previous toast has not disappeared
    
        // 忽略以下逻辑
        // ...
        // ...
        // ...
    
        return toast;
    }
    

    1.3 再去查看 createInstance 函数实现

    这里使用 !queue.length || multiple || !isInDocument(queue[0].$el) 作为判断条件来实现 单例模式

    !queue.length 若创建的 toast 列表中,长度为 0,则可进入创建逻辑
    
    multiple 若支持创建多个 toast ,则可进入创建逻辑
    
    !isInDocument(queue[0].$el) 若在页面 body 中,检测不到 toast 元素,则可进入创建逻辑
    
    // vant 1.6.16
    // vant/es/toast/index.js
    function createInstance() {
        /* istanbul ignore if */
        if (isServer) {
            return {};
        }
    
        if (!queue.length || multiple || !isInDocument(queue[0].$el)) {
            // 手动加入的调试语句
            // debugger
            console.log('
    queue :>> ', queue.length);
            console.log('multiple :>> ', multiple);
            console.log('!isInDocument(queue[0].$el :>> ', !isInDocument(queue[0] && queue[0].$el))
            console.log(document.body.contains(queue[0] && queue[0].$el))
            var toast = new (Vue.extend(VueToast))({
            el: document.createElement('div')
            });
            queue.push(toast);
        }
    
        return queue[queue.length - 1];
    } // transform toast options to popup props
    
    // vant/es/utils/index.js
    export function isInDocument(element) {
        return document.body.contains(element);
    }
    

    在页面中查看输出语句,发现第二次调用 this.$loading 的时候,也进入了创建逻辑,明显不符合我们的要求

    根据输出条件,判定是 !isInDocument(queue[0] && queue[0].$el) 为 true 造成逻辑命中,进入了创建逻辑

    该语句表示若在页面 body 中不存在 toast 元素,则为 true

    说明判断是不存在 toast ,但是前面确实已经创建了一个 toast


    1.4 那么为什么呢?

    在条件语句下面 加入 debugger 语句,进行断点调试:
    
    1、执行创建一次 toast 之后,第二次进入创建逻辑,在 console 控制台 打印 queue
    
    2、此时 queue 中只有一个元素,是 Vue 组件对象,打开 里面的 $el 发现不是一个 dom 对象
    且此时页面空白,说明还未挂载
    
    3、这时候执行 document.body.contains(element) 肯定会返回 false
    因为我们在 created 中调用 Toast.loading , 此时组件还没被挂载, toast 元素还没有插入到 dom 中,问题出在这里
    


    2. Toast.clear

    Toast.clear 的时候若传入参数为 true,则关闭所有的 toast

    若不支持创建多个,则关闭 toast 列表中的第一个 toast

    Toast.clear = function (all) {
      if (queue.length) {
        if (all) {
          queue.forEach(function (toast) {
            toast.clear();
          });
          queue = [];
        } else if (!multiple) {
          queue[0].clear();
        } else {
          queue.shift().clear();
        }
      }
    };
    

    三、解决

    知道问题是因为组件未挂载造成条件判断问题,因而创建了多个 toast 实例

    那么相应的解决方法有两个

    1、不在 created 中进行 loading 操作,或者不多于一次 loading 操作
    
    2、Toast.clear() 方法,加入 true 参数: Toast.clear(true),关闭所有的 toast 实例
    

    四、vant 更新修复

    因为历史原因,项目使用的是 vant 1.6x 的版本

    发现 vant 有 bug 还想去提个 pr 或者 issue

    但是查看了 vant 2.x 版本已经修复了这个问题

    其实就是去掉了上面那个 isInDocument 方法判断

    // vant 2.2.16
    function createInstance() {
        /* istanbul ignore if */
        if (isServer) {
            return {};
        }
    
        // 此处去掉了 1.6x 版本中 isInDocument 导致的问题
        if (!queue.length || multiple) {
            var toast = new (Vue.extend(VueToast))({
            el: document.createElement('div')
            });
            toast.$on('input', function (value) {
            toast.value = value;
            });
            queue.push(toast);
        }
    
        return queue[queue.length - 1];
    } // transform toast options to popup props
    
  • 相关阅读:
    C# WinForm下,隐藏主窗体,只在进程管理器中显示进程,在任务栏,状态栏都不显示窗体的方法
    C#全能数据库操作类及调用示例
    多个汇总列转换为行记录 mssql
    Oracle 10g创建数据库 用户等基本操作
    Jquery基本选择器 层次选择器 过滤选择器 表单选择器使用示例 带注释
    SQL与ORACLE的外键约束级联更新和删除
    C# 屏幕监控 自动截屏程序 主窗体隐藏,仅在进程中显示
    图文讲解VS2010程序打包操作 安装卸载
    查表法按日期生成流水号 mssql
    给DataTable添加主键 几何级提升Select筛选数据的速度
  • 原文地址:https://www.cnblogs.com/linjunfu/p/13038113.html
Copyright © 2011-2022 走看看