在Ant Design
组件库中,Tree
组件只支持点击父节点左侧的展开按钮进行展开
/收缩
操作,但是展开按钮很小,有时会不便于操作,这里展示了通过利用Tree组件的expandedKeys
和onExpand
属性,将Tree
组件的展开收缩改为受控行为来完成这一功能。
模拟数据
自定义data
模拟数据
javascript
// Tree组件模拟数据
const data = [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{
title: '0-0-0-0',
key: '0-0-0-0',
},
{
title: '0-0-0-1',
key: '0-0-0-1',
},
],
},
{
title: '0-0-1',
key: '0-0-1',
},
],
},
{
title: '0-2',
key: '0-2',
},
]
属性定义
定义Tree
组件数据属性treeData
及受控属性expandedKeys
/onExpand
javascript
const [treeData, setTreeData] = useState([])
const [expandedKeys, setExpandedKeys] = useState([])
const handleExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
}
数据生成
通过data
数据递归生成树节点数据,并定义树节点的点击事件来控制树节点的展开
/收缩
jsx
// 递归生成树节点
const getTreeItem = useCallback(
(item) => {
const treeItem = {
title: <div onClick={() => handleClick(item.title, treeItem.children)}>{item.title}</div>,
key: item.title,
}
if (item.children) {
treeItem.children = item.children.map((item) => getTreeItem(item))
}
return treeItem
},
[handleClick],
)
// 树节点点击事件
const handleClick = useCallback(
(key, children) => {
// 判断当前点击的节点key是否在expandedKeys中
const index = expandedKeys.findIndex((item) => item === key)
if (index === -1) {
// 如果不在expandedKeys中并且该节点有子节点,则将当前节点key添加到expandedKeys中(展开)
children?.length &&
setExpandedKeys((keys) => {
const newKeys = [...keys, key]
return newKeys
})
} else {
// 如果在expandedKeys中,则将当前节点key从expandedKeys中删除(收缩)
setExpandedKeys((keys) => keys.filter((item) => item !== key))
}
},
[expandedKeys],
)
useEffect(() => {
// 初始化树节点(这里可以根据实际情况进行获取数据)
const treeData = data.map((item) => getTreeItem(item))
setTreeData(treeData)
}, [])
Tree组件使用
jsx
<Tree
showLine
showIcon
blockNode
selectable={false}
treeData={treeData}
expandedKeys={expandedKeys}
onExpand={handleExpand}
/>
动画优化
完成后发现可以实现点击节点进行展开
/收缩
功能,但是丢失了展开/收缩时的动画,下面是利用React Hook
中的useDeferredValue
来解决展开
/收缩
时的动画效果丢失问题。
jsx
//通过useDeferredValue来获取expandKeys的延迟版本
const deferredExpandedKeys = useDeferredValue(expandedKeys)
//...
//在Tree组件中用deferredExpandedKeys来代替expandedKeys
<Tree
showLine
showIcon
blockNode
selectable={false}
treeData={treeData}
expandedKeys={deferredExpandedKeys}
onExpand={handleExpand}
/>
完整代码
jsx
import React, { useState, useCallback, useEffect, useDeferredValue } from 'react'
import { Tree } from 'antd'
// Tree组件模拟数据
const data = [
{
title: '0-0',
key: '0-0',
children: [
{
title: '0-0-0',
key: '0-0-0',
children: [
{
title: '0-0-0-0',
key: '0-0-0-0',
},
{
title: '0-0-0-1',
key: '0-0-0-1',
},
],
},
{
title: '0-0-1',
key: '0-0-1',
},
],
},
{
title: '0-2',
key: '0-2',
},
]
const App = () => {
const [treeData, setTreeData] = useState([])
const [expandedKeys, setExpandedKeys] = useState([])
const deferredExpandedKeys = useDeferredValue(expandedKeys)
const handleExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
}
// 树节点点击事件
const handleClick = useCallback(
(key, children) => {
// 判断当前点击的节点key是否在expandedKeys中
const index = expandedKeys.findIndex((item) => item === key)
if (index === -1) {
// 如果不在expandedKeys中并且该节点有子节点,则将当前节点key添加到expandedKeys中(展开)
children?.length &&
setExpandedKeys((keys) => {
const newKeys = [...keys, key]
return newKeys
})
} else {
// 如果在expandedKeys中,则将当前节点key从expandedKeys中删除(收缩)
setExpandedKeys((keys) => keys.filter((item) => item !== key))
}
},
[expandedKeys],
)
// 递归生成树节点
const getTreeItem = useCallback(
(item) => {
const treeItem = {
title: <div onClick={() => handleClick(item.title, treeItem.children)}>{item.title}</div>,
key: item.title,
}
if (item.children) {
treeItem.children = item.children.map((item) => getTreeItem(item))
}
return treeItem
},
[handleClick],
)
useEffect(() => {
// 初始化树节点(这里可以根据实际情况进行获取数据)
const treeData = data.map((item) => getTreeItem(item))
setTreeData(treeData)
}, [])
return (
<Tree
showLine
showIcon
blockNode
selectable={false}
treeData={treeData}
expandedKeys={deferredExpandedKeys}
onExpand={handleExpand}
/>
)
}
export default App