本示例节选自vue3最新开源组件实战教程大纲(持续更新中)的tree组件开发部分。考察响应式对象列表封装和computed
计算属性的使用,以及数组reduce
方法实现结构化树拍平处理的核心逻辑。
实现思路
第一种方式:每次折叠或展开后触发扁平化列表的重新计算和渲染。
第二种方式:每次折叠或展开后出发其所有子节点的visible
计算属性重新计算,用v-show
进行动态渲染。
这里我们先讨论第一种方案的实现。
实现方式
在循环遍历树节点时,增加要渲染的内容:
<div class='juan-tree-node' key=... style=...>
{/* 渲染节点前的内容,父节点需要展示隐藏或展开按钮,叶子节点留出空的span来占据位置 */}
{node.isLeaf ? (
<span class='mr-1 inline-block w-[20px]'></span>
) : (
<button onClick={() => toggleNode(node)} class='mr-1 inline-block h-[18px] w-[20px]'>
{node.expanded ? <span>-</span> : <span>+</span>}
</button>
)}
{node[labelName as 'label']}
</div>
节点的展开与折叠操作,注意需要操作响应式的对象,这样会触发计算属性的重新计算:
// 将树拍平的扁平化列表结构,包装成响应式列表,操作一个节点的展开与折叠,其实操作的是响应式列表中的一个元素,以便触发expandedTree计算属性重新计算。
const flatData = ref(generateFlatTree(data, optionProps))
const toggleNode = (node: IFlatTreeNode) => {
// 注意,这里操作的其实是响应式列表中的一个元素,改变其属性会触发expandedTree重新计算
node.expanded = !node.expanded
}
// 该计算属性用于计算已经展开的所有节点,包括手动设置expanded属性为false的父节点,而排除掉扁平化列表中折叠节点的所有子孙节点。
const expandedTree = computed(() => {
const result = []
for (let i = 0; i < flatData.value.length; i++) {
const item = flatData.value[i]
// 如果是父节点并且非展开(默认是折叠的)则跳过其所有子孙节点。
if (!item.isLeaf && item.expanded !== true) {
// 跳过内部所有节点
i += item.length!
}
result.push(item)
}
return result
})
不要忘了,tree模板中遍历的对象为expandedTree.value
。
跳过多少个需要隐藏的子节点列表呢?
这是expandedTree计算属性实现中的关键点,咱们的实现是给
IFlatTreeNode
上加一个length
属性,
// 扩展的扁平化节点
export interface IFlatTreeNode extends ITreeNode {
...
length?: number // 设置其子孙节点的长度
}
该属性值是在generateFlatTree
拍平函数中设置的:
执行效果:
存在的问题
存在的问题
expandedTree
计算属性每次都要整体进行计算,且对变化的部分做重新渲染IFlatTreeNode
的length
属性是在拍平函数中设置死的,append节点后,这个属性值应该是变化的。
下一小节,我们讲采用第二种实现方式来规避这个问题。