假如我们拥有这么一个需求:当一组按钮超出三个时只展示两个按钮,其他的收在下拉菜单之中展示成 ...
或者 更多操作
,点击下拉菜单再展示收起的按钮。
糟糕的方案
通常我们的按钮组是受条件动态控制的,不同的条件下会展示不同的按钮。
<button v-if="can1">按钮1</button>
<button v-if="can2">按钮2</button>
<button v-if="can3">按钮3</button>
<button v-if="can4">按钮4</button>
<button v-if="can5">按钮5</button>
因此我们直接下面这样写不太合适:
<button v-if="can1">按钮1</button>
<button v-if="can2">按钮2</button>
<button v-if="can3">按钮3</button>
<el-dropdown>
...
<el-dropdown-menu slot="dropdown">
<el-dropdown-item> v-if="can4">按钮4</el-dropdown-item>
<el-dropdown-item> v-if="can5">按钮5</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
一种 可怕
的实现是尝试通过大量的 if
逻辑来尝试枚举出所有可能场景
<button v-if="can1">按钮1</button>
<button v-if="can2">按钮2</button>
<button v-if="can3">按钮3</button>
<button v-if="can4 && (!can1 || !can2 || !can3)">按钮4</button>
<button v-if="can5 && ((!can1 && (!can2 || !can3 || !can4)) || ...)">按钮5</button>
<el-dropdown v-if="....">
...
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="can4">按钮4</el-dropdown-item>
<el-dropdown-item v-if="can5">按钮5</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
我们也许可能会尝试通过封装 一个组件
和通过 两个计算属性
来控制按钮组的陈列:
<template>
<button v-for="item in outBtns" @click="item.click" :key="item.id">{{item.text}}</button>
<el-dropdown v-if="sideBtns.length">
...
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in sideBtns" @click.stop="item.click" :key="item.id">
{{item.text}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<template>
// 为了减少对象的创建 btns 可以通过 import 导入
const btns = [
{ text: '按钮1', click: (row) => {}, id: 1, checkShow: row => '...布尔表达式' },
{ text: '按钮2', click: (row) => {}, id: 2, checkShow: row => '...布尔表达式' },
{ text: '按钮3', click: (row) => {}, id: 3, checkShow: row => '...布尔表达式' },
{ text: '按钮4', click: (row) => {}, id: 4, checkShow: row => '...布尔表达式' },
{ text: '按钮5', click: (row) => {}, id: 5, checkShow: row => '...布尔表达式' }
]
export default {
props: [ 'row'],
computed: {
showBtns () {
return btns.filter(btn => btn.checkShow(this.row))
},
outBtns () {
if (this.showBtns.length <= 3) {
return this.showBtns
}
return this.showBtns.slice(0, 2)
},
sideBtns () {
if (this.showBtns.length <= 3) {
return []
}
return this.showBtns.slice(2)
}
}
}
用这种方式来实现无法做到自动兼容新增的按钮,需要手动维护 btns
的数据,不太友好。
最佳方案
我们可以使用 render函数控制slot渲染
和 函数式组件
来实现这个需求,它即考虑了性能也保证了自动兼容后面的按钮添加,还可以感受到 render函数
的强大。
export default {
functional: true,
render(h, vm) {
// 过滤掉 v-if 为 false的节点
const nodes = vm.children.filter(e => e.tag)
if (nodes.length <= 3) {
return h('div', nodes)
} else {
const outBtns = [nodes[0], nodes[1]]
const sideBtns = nodes.slice(2)
return (
<div>
{ h('div', outBtns) }
<el-dropdown>
<span>
更多操作<i class="el-icon-arrow-down"></i>
</span>
<el-dropdown-menu slot="dropdown">
{ sideBtns.map(item => <el-dropdown-item>{ h('span', [item]) }</el-dropdown-item>) }
</el-dropdown-menu>
</el-dropdown>
</div>
)
}
}
}
使用方式如下:
<!-- btn-auto-side是我们封装的函数式组件 -->
<btn-auto-side>
<button v-if="can1">按钮1</button>
<button v-if="can2">按钮2</button>
<button v-if="can3">按钮3</button>
<button v-if="can4">按钮4</button>
<button v-if="can5">按钮5</button>
</btn-auto-side>
这种方案非常友好的实现了这个需求:按钮超出后自动折叠。