Appearance
从0到1搭建Vue组件库03:如何给 tree 组件增加展开/收起功能
最近在与村长老师一起做直播,给大家分享vue devui开源组件库的建设,前面两期以从 0 开始开发一个 tree 组件为栗子🌰,介绍了如何实现一个能渲染多层节点的 tree 组件。
最终实现的效果如下:
这只是实现了渲染的逻辑,tree 节点前面的减号图标是无法点击的,节点是无法收起的。
这次就将带大家一起实现点击图标展开/收起树节点的功能。
最终效果
我们需要实现的最终效果如下:
增加open标识
之前传入 tree 组件的 data 大致结构是这样的:
[
{
label: '一级 1', level: 1,
children: [{
label: '二级 1-1', level: 2,
}]
},
...
]
这样我并不知道哪些节点需要展开,哪些需要收起,所以第一步应该给需要展开的节点增加open字段。
比如我们希望让以下节点展开,其他都收起:
- 一级 2
- 一级 3
- 二级 3-2
改造后的数据结构如下:
[
{
label: '一级 1', level: 1,
children: [...]
},
{
label: '一级 2', level: 1,
open: true, // 新增
children: [...]
},
{
label: '一级 3', level: 1,
open: true, // 新增
children: [{
label: '二级 3-2', level: 2,
open: true, // 新增
children: [...]
}]
},
{
label: '一级 4', level: 1,
}
]
渲染展开/收起图标
没有open字段的情况下,节点默认是全部展开的,节点前面的图标全部都是标识展开的减号图标。
现在有了open字段,我们可以根据该字段渲染展开(减号) or 收起(加号)图标,因此我们需要改造下renderNode
方法。
const renderNode = (item) => {
return (
<div
class="devui-tree-node"
style={{ paddingLeft: `${24 * (item.level - 1)}px` }}
>
{
item.children
// Before
// ? <IconOpen class="mr-xs" />
// After
? item.open
? <IconOpen class="mr-xs" />
: <IconClose class="mr-xs" />
: <Indent />
}
{ item.label }
</div>
)
}
基本渲染逻辑
- 如果当前节点没有子节点,则直接渲染,节点无图标,根据当前层级显示相应数量的占位元素 Indent
- 如果当前节点有子节点,open 属性不为 true,则直接渲染(不渲染子节点),前面的图标为 IconClose
- 如果当前节点有子节点,open 属性为 true,则渲染当前节点+它的第一层子节点,前面的图标为 IconOpen
- 如果子节点中又包含 open 为 true 的节点,则以此类推
只渲染展开的节点
为了方便渲染制定的节点,我们对之前的嵌套数据结构进行一些转换:
- 将数据拍平
- 过滤出 open 为 true 的节点数据
转换的基本思路是:
- 通过 reduce 方法进行递归,初始值为空数组
[]
- 然后判断 item 数据是否有 open 属性
- 有的话将该数据+子数据都拼接起来
- 没有的话就只将该数据进行拼接
// 获取需要展开的节点数据(无嵌套结构的一维数组)
const openedTree = (tree) => {
return tree.reduce((acc, item) => (
item.open
? acc.concat(item, openedTree(item.children))
: acc.concat(item)
), [])
}
const openedData = openedTree(data)
到这一步效果就已经有了,只是还不能交互。
给节点绑定点击事件
要实现点击图标展开/收起节点功能,就需要给节点图标绑定点击事件。
const renderNode = (item) => {
return (
<div
class="devui-tree-node"
style={{ paddingLeft: `${24 * (item.level - 1)}px` }}
>
{
item.children
? item.open
? <IconOpen class="mr-xs" onClick={() => toggle(item)} /> // 给节点绑定点击事件
: <IconClose class="mr-xs" onClick={() => toggle(item)} /> // 给节点绑定点击事件
: <Indent />
}
{ item.label }
</div>
)
}
const toggle = (item) => {
// 展开/收起逻辑
}
处理展开/收起的逻辑
展开/收起功能,本质上就是改变当前节点数据的 open 字段:
- 如果当前 open 字段为 true,说明节点是展开的,点击图标时,应该将其设置为 false
- 如果当前没有 open 字段或者 open 字段为 false,说明节点是收起的,点击图标时,应该将其设置为 true
const toggle = (item) => {
item.open = !item.open // 改变当前节点的open字段
}
这样我们的目标就完成了:
实现能展开/收起的 tree
不过目前代码都写在 tree 组件的 setup 方法里,加上之前的 renderNode 等方法,setup 方法已经有60+行代码,后续如果继续增加其他功能,setup 代码量会越来越大,也越来越不可读和难以维护,也就越容易出 bug。
因此需要对它进行重构,使用 vue3 的 composition api,将节点展开/收起相关的变量和逻辑抽离到一个单独的use-toggle.ts
文件中。
composables/use-toggle.ts
import { ref } from 'vue'
export default function useToggle(data: unknown): any {
const openedTree = (tree) => {
return tree.reduce((acc, item) => (
item.open
? acc.concat(item, openedTree(item.children))
: acc.concat(item)
), [])
}
const openedData = ref(openedTree(data)) // 响应式对象
const toggle = (item) => {
console.log('toggle', item, item.id, item.open);
item.open = !item.open
openedData.value = openedTree(data)
}
return {
openedData,
toggle,
}
}
tree.tsx 中只需要引入需要的变量和方法即可。
import useToggle from './composables/use-toggle'
setup(props) {
// 其他逻辑
// 从 useToggle 中引入需要的变量和方法
const { openedData, toggle } = useToggle(data.value)
// 其他逻辑
}
小结
本文主要讲述如何一步步给 tree 组件增加展开/收起功能,并使用vue3的组合式api对这个功能从setup 中抽离。
发布时间: