前段时间把项目的脚手架升级到 VueCLI 5
同步也升级了一些依赖项。所以这几天都是在自测,看看是不是有一些很明显的 BUG,以及解决一些依赖升级之后的 WARN
。
在测试一个下拉菜单(Dropdown
)时,发现了一个问题:使用方向键操控会抛出一个异常:Uncaught TypeError: ele is undefined
查看控制台输出,对应异常抛出的位置是 resetTabindex 函数,在 ele.setAttribute('tabindex', '0')
执行时 ele
是 undefined
,所以抛出了这个异常。
那么看函数调用链看 ele
是怎么来的。
👉 是 handleItemKeyDown
函数执行传入的 this.menuItems[nextIndex]
进入调试模式之后发现整个 menuItems
数组是空数组,并没有把我在模板中渲染的 el-dropdown-item
都包含进来。
捋了一下 el-dropdown
组件的业务逻辑,发现 el-dropdown-menu
组件会在完成挂载之后(mounted
)立即执行 dropdown
组件的 initDomOperation
函数。
其他的任意时机都不会再次执行 initDomOperation
方法了。所以问题就是在 el-dropdown-menu
组件完成挂载时,我在模板中渲染的 el-dropdown-item
组件都没有被 querySelectorAll
方法查询到。也就是说并没有被正确渲染在DOM树上。
确实,业务中使用 v-for
循环的数据是通过接口返回的。el-dropdown-menu
组件完成挂载时接口数据是不可能及时返回,所以 querySelectorAll
获取不到任何的 el-dropdown-item
元素。
那么解决方案就很简单了:
- 使用
v-if
包裹el-dropdown
或者el-dropdown-menu
组件。 - 或者在接口返回数据后手动调用
el-dropdown
组件的initDomOperation
方法。
最后再补充一个 Dropdown
组件使用中遇到的其他问题:
💥 在 el-dropdown
组件中使用 el-input
作为 el-dropdown-item
的内容物时,点击 el-input
元素或者回车都会导致 el-dropdown-menu
收起。
这个需求是为了长下拉菜单中,可以通过键入
keyword
过滤下拉菜单,并且可以使用回车快速选中筛选的第一项。
这个问题很简单把 el-dropdown
组件的 hide-on-click
属性置为 false
即可。
但是这样做会导致一个其他的问题:点击正常的 el-dropdown-item
元素(内容物不是 el-input
的菜单项)时,el-dropdown-menu
也不会收起了。
场景1:如果你不需要在 el-input
元素中回车可以选择第一项菜单这样的需求,那么只需要在 el-input
使用 .stop
修饰符拦截事件冒泡。
注意: 不需要将
el-dropdown
组件的hide-on-click
属性置为false
<el-dropdown>
<span class="el-dropdown-link">
下拉菜单<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<el-input v-model="keyword" @click.native.stop="() => ({})" />
</el-dropdown-item>
<el-dropdown-item>黄金糕</el-dropdown-item>
<el-dropdown-item>狮子头</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
场景2:和我的需求一样,期望在 el-input
中回车仍可以完成选择菜单第一项,那么就需要手动控制 el-dropdown
组件的 visible
属性。
完整的示例代码 👇
<template>
<el-dropdown ref="elDropdown" trigger="click" :hide-on-click="false" @command="handleCommand">
<span class="el-dropdown-link">
点击展开菜单<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<el-input v-model="keyword" />
</el-dropdown-item>
<el-dropdown-item v-for="item in displayOptionList" :key="item" :command="item">
{{ item }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
name: "TEST",
data() {
return {
keyword: '',
optionList: ['黄金糕', '狮子头', '螺蛳粉', '双皮奶', '蚵仔煎']
};
},
computed: {
displayOptionList() {
if(this.keyword === '') return this.optionList
return this.optionList.filter(str => str.includes(this.keyword))
}
},
methods: {
handleCommand(key) {
if (key) {
this.$refs.elDropdown.visible = false
this.$nextTick(() => {
this.keyword = ''
})
alert(`您选择的是:${key}`)
} else {
if (this.keyword !== '') {
const firstOption = this.displayOptionList[0]
if(firstOption) this.handleCommand(firstOption)
}
}
}
}
}
</script>
记得将 el-dropdown
组件的 hide-on-click
属性置为 false
。如果不设置为 false
,依靠 @click.native.stop="() => ({})"
来取消 el-input
元素的 click
事件冒泡。那么在 el-input
中回车并不会触发 el-dropdown
组件的 command
事件。并且因为 el-input
没有正确抛出 keydown/keyup
相关的事件,想要单独处理就会非常麻烦。el-dropdown
会注册 keydown
事件监听,并直接拦截掉事件冒泡和默认行为。