当采用递归方式生成导航栏的子菜单时,菜单可以正常生成,但是当鼠标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” 因此即使添加了这个属性,也依然是报错的,在此致歉!
暂无评论内容