详解Element-ui NavMenu子菜单使用递归生成时使用报错

当采用递归方式生成导航栏的子菜单时,菜单可以正常生成,但是当鼠标hover时,会出现循环调用某个(mouseenter)事件,导致最后报错

处理方式

注:2.13.2 版本,只需对子菜单设置属性 :popper-append-to-body="false" 就不会出现这个问题了

报错信息如下:

Uncaught RangeError: Maximum call stack size exceeded.
    at VueComponent.handleMouseenter (index.js:1)
    at invokeWithErrorHandling (vue.js:1863)
    at HTMLLIElement.invoker (vue.js:2188)
    at HTMLLIElement.original._wrapper (vue.js:7547)
    at VueComponent.handleMouseenter (index.js:1)
    at invokeWithErrorHandling (vue.js:1863)
    at HTMLLIElement.invoker (vue.js:2188)
    at HTMLLIElement.original._wrapper (vue.js:7547)
    at VueComponent.handleMouseenter (index.js:1)
    at invokeWithErrorHandling (vue.js:1863)

测试代码

版本:

  • vue: v2.6.11
  • element-ui: 2.13.0
<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title></title>
  <!-- 引入样式 -->
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" rel="external nofollow" >
 </head>
 <body>

  <div id="root">
   <el-menu mode="horizontal">
    <template v-for="(menu,index) in menus">
     <sub-menu v-if="menu.children && menu.children.length" :key="index" :item="menu"></sub-menu>
     <el-menu-item v-else :index="menu.path" :key="index">{{ menu.title }}</el-menu-item>
    </template>
   </el-menu>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <!-- 引入组件库 -->
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <script type="text/javascript">
   Vue.component('sub-menu', {
    props: ['item'],
    template: `
     <el-submenu :index="item.path">
      <template slot="title">
       {{item.title}}
      </template>
      <template v-for="(child,index) in item.children">
       <sub-menu v-if="child.children" :item="child" :key="index"></sub-menu>
       <el-menu-item v-else :key="index" :index="child.path">
        {{child.title}}
       </el-menu-item>
      </template>
     </el-submenu>
    `
   })

   let vm = new Vue({
    el: '#root',
    data() {
     return {
      menus: [{
       title: '我的工作台',
       path: '2',
       children: [{
         title: '选项1',
         path: '2-1'
        },
        {
         title: '选项2',
         path: '2-2',
        },
       ],
      },{
       title:'后台管理',
       path:'3'
      }]
     }
    },
    components: {}
   })
  </script>
 </body>
</html>

错误分析

观察递归生成的导航栏代码及报错代码:

 handleMouseenter: function(e) {
                    var t = this
                      , i = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : this.showTimeout;
                    if ("ActiveXObject"in window || "focus" !== e.type || e.relatedTarget) {
                        var n = this.rootMenu
                          , r = this.disabled;
                        "click" === n.menuTrigger && "horizontal" === n.mode || !n.collapse && "vertical" === n.mode || r || (this.dispatch("ElSubmenu", "mouse-enter-child"),
                        clearTimeout(this.timeout),
                        this.timeout = setTimeout(function() {
                            t.rootMenu.openMenu(t.index, t.indexPath)
                        }, i),
                        this.appendToBody && this.$parent.$el.dispatchEvent(new MouseEvent("mouseenter")));//报错代码
                    }
                },

猜测是因为事件冒泡或下沉导致元素重复派发和接受mouseenter事件,造成了类似死循环的状态,因时间关系,没做深究,后面有时间的时候再查下根本原因(如果记得的话…)

当鼠标移入到菜单中时,触发handleMouseenter方法,但是因为appendToBody为true,所以又派发了鼠标移入事件,然后又回到了这个方法,由此造成了死循环。appendToBody是一个计算属性,那么为什么appendToBody会是true呢?看代码:

{
 name: 'ElSubmenu',
    componentName: 'ElSubmenu',
 props:{
  popperAppendToBody: {
         type: Boolean,
         default: undefined
        }
    },
    computed:{
  appendToBody() {
        return this.popperAppendToBody === undefined
          ? this.isFirstLevel              //未显示指明popperAppendToBody 时,计算这个值
          : this.popperAppendToBody;
      },
      isFirstLevel() {
        let isFirstLevel = true;
        let parent = this.$parent;
        while (parent && parent !== this.rootMenu) {

        //计算当前是否时第一级菜单。
        //看上去是没问题的,因为代码里已经指明了当前的组件名是 componentName: 'ElSubmenu', 但是在调试中发现,componentName的值是Undefined, 因此不管是在哪一级,最后的结果都是 isFirstLevel = true
          if (['ElSubmenu', 'ElMenuItemGroup'].indexOf(parent.$options.componentName) > -1) {
            isFirstLevel = false;
            break;
          } else {
            parent = parent.$parent;
          }
        }
        return isFirstLevel;
      }
 }
}

至于为什么vue在组件注册时没有收集这个参数,还需要从源码那边看,午休时间过了,要继续撸代码了…得空了再分析一下…

处理方式

给el-submenu添加一个属性 :popper-append-to-body=“true false” 显式的指明appendToBody为false

特别致歉:

此前的处理方式写错了,写的是:popper-append-to-body=“true” 因此即使添加了这个属性,也依然是报错的,在此致歉!

到此这篇关于详解Element-ui NavMenu子菜单使用递归生成时使用报错的文章就介绍到这了,更多相关Element-ui NavMenu子菜单递归生成内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 解决element-ui中下拉菜单子选项click事件不触发的问题

    将@click改为@click.native='logoutHandle';即可监听选项的点击事件. <el-dropdown class="pull-right" > <span class="el-dropdown-link"> <img src="./img/header.jpg" alt="" class="pull-right"> </span> &

  • 详解Element-ui NavMenu子菜单使用递归生成时使用报错

    当采用递归方式生成导航栏的子菜单时,菜单可以正常生成,但是当鼠标hover时,会出现循环调用某个(mouseenter)事件,导致最后报错 处理方式 注:2.13.2 版本,只需对子菜单设置属性 :popper-append-to-body="false" 就不会出现这个问题了 报错信息如下: Uncaught RangeError: Maximum call stack size exceeded.     at VueComponent.handleMouseenter (inde

  • 详解element上传组件before-remove钩子问题解决

    应公司业务要求已上传文件删除前提醒确认代码如下 if(file && file.status === "success"){ return this.$confirm('此操作将永久删除该文件, 是否继续?', '系统提示',{ confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', center: true }).then(() => { this.$message({ type: 's

  • 详解在idea 中使用Mybatis Generator逆向工程生成代码

    通过MAVEN完成 Mybatis 逆向工程 1. POM文件中添加插件 在 pom 文件的build 标签中 添加 plugin 插件和 数据库连接 jdbc 的依赖. <build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <

  • 详解Java中二叉树的基础概念(递归&迭代)

    目录 1.树型结构 1.1概念 1.2概念(重要) 2.二叉树(重点) 2.1概念 2.2二叉树的基本形态 2.3两种特殊的二叉树 2.4二叉树的性质 2.5二叉树的存储 2.6二叉树的基本操作 2.7二叉树的层序遍历 3.二叉树完整代码 1. 树型结构 1.1概念 树是一种 非线性 的数据结构,它是由 n ( n>=0 )个有限结点组成一个具有层次关系的集合. 把它叫做树是因为它看 起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的 . 1.2 概念(重要) a.节点的度:该节点子树的个数:如

  • 详解Angular路由之子路由

    目录 一.子路由语法 二.实例 1.新建2个组件修改其内容 2.修改路由配置 3.修改product.component.ts的模版 一.子路由语法 二.实例 在商品详情页面,除了显示商品id信息,还显示了商品描述,和销售员的信息. 通过子路由实现商品描述组件和销售员信息组件展示在商品详情组件内部. 1.新建2个组件修改其内容 ng g component productDesc ng g component sellerInfo 重点是修改销售员信息组件,显示销售员ID. import { C

  • 实例详解AngularJS实现无限级联动菜单

    多级联动菜单是常见的前端组件,比如省份-城市联动.高校-学院-专业联动等等.场景虽然常见,但仔细分析起来要实现一个通用的无限分级联动菜单却不一定像想象的那么简单.比如,我们需要考虑子菜单的加载是同步的还是异步的?对于初始值的回填发生在前端还是后端?如果异步加载,是否对于后端API的返回格式有严格的定义?是否容易实现同步.异步共存?是否可以灵活的支持各类依赖关系?菜单中是否有空值选项?--一系列的问题都需要精心处理. 带着这些需求搜索了一圈,不太出乎意料,并没有能在AngularJS的生态中找到一

  • 详解Element 指令clickoutside源码分析

    clickoutside是Element-ui实现的一个自定义指令,顾名思义,该指令用来处理目标节点之外的点击事件,常用来处理下拉菜单等展开内容的关闭,在Element-ui的Select选择器.Dropdown下拉菜单.Popover 弹出框等组件中都用到了该指令,所以这个指令在实现一些自定义组件的时候非常有用. 要分析该源码,首先要了解一下Vue的自定义指令.自定义指令的定义方式如下: // 注册一个全局自定义指令 Vue.directive('directiveName', { bind:

  • 详解Android UI更新的几种方法

    前言 在android开发中,界面UI的更新都是在主线程来完成的.线程分为主线程(Main Thread,简称MT)和工作线程(Work Thread,简称WT),我们通常会在WT中执行一些比较耗时的操作,比如下载,网络,缓存等,然后在将结果发送给MT进行UI的更新操作.如果是在WT进行UI的更新,则会抛出异常,android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created

  • 详解jQuery UI库中文本输入自动补全功能的用法

    自动补全(autocomplete),是一个可以减少用户输入完整信息的UI 工具.一般在 输入邮箱.搜索关键字等,然后提取出相应完整字符串供用户选择. 一.调用autocomplete()方法 $('#email').autocomplete({ source : ['aaa@163.com', 'bbb@163.com', 'ccc@163.com'], }); 二.修改autocomplete()样式    由于autocomplete()方法是弹窗,然后鼠标悬停的样式.通过Firebug

  • bootstrap响应式导航条模板使用详解(含下拉菜单,弹出框)

    本文实例为大家分享了bootstrap响应式导航条模板展示的具体代码,供大家参考,具体内容如下 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name=&quo

随机推荐