Tree树形组件开发实战与性能优化经验分享
为啥我要折腾 Tree 组件?
最近项目里又遇到树形结构的需求,用户权限配置、分类管理、文件目录……绕不开。但每次用 Tree 都像在“选老婆”——没有完美的,只有更合适的。市面上主流方案就那几个:Ant Design 的 Tree、Element Plus 的 Tree、自己手写递归组件,还有用虚拟滚动优化的高性能方案。我踩过坑、也省过事,今天就唠点实在的。
谁更灵活?谁更省事?
先说结论:小项目我直接上 Ant Design,大列表必须自己撸 + 虚拟滚动。别听厂商吹“开箱即用”,Tree 这玩意儿一旦数据量上来,性能和交互细节立马露馅。
比如 Ant Design 的 Tree,API 确实省心:
import { Tree } from 'antd';
const MyTree = () => {
const treeData = [
{ title: '父节点1', key: '0-0', children: [
{ title: '子节点1-1', key: '0-0-1' },
{ title: '子节点1-2', key: '0-0-2' }
]},
// ...
];
return <Tree treeData={treeData} />;
};
三行代码搞定,checkbox、展开收起、异步加载全都有。但问题来了——当节点超过 500 个,页面直接卡成 PPT。我试过 virtualized 属性(v5 才有),但定制样式麻烦,而且和我们 UI 设计对不上,改起来比从头写还累。
Element Plus 的 Tree 呢?差不多,甚至更弱一点。文档少、事件回调不全,比如我想监听“某个节点被点击时是否处于选中状态”,它得自己维护一个外部 state,而 Ant Design 直接在 onCheck 里给你回传 checkedKeys 和 halfCheckedKeys,省不少事。所以中小项目我首选 Ant Design,除非你用 Vue 生态且不想引入 React。
性能对比:差距比我想象的大
上个月有个需求,要展示 3000+ 的组织架构树,每个节点带图标、操作按钮、状态标签。我先用 Ant Design 试了,结果 Chrome DevTools 的 Performance 面板一跑,点击展开一个节点居然花了 800ms!用户反馈“点一下等半天”。
折腾了半天发现,问题出在**全量渲染**。Tree 组件默认把所有节点都塞进 DOM,哪怕没展开。这时候只能自己写递归组件 + 虚拟滚动。
核心思路就两点:
- 只渲染当前可视区域的节点(用 react-window 或 vue-virtual-scroll-list)
- 节点展开/收起时动态增删子节点,而不是靠 CSS 隐藏
下面是我简化后的递归组件核心逻辑(React):
const TreeNode = ({ node, level = 0, onToggle, onCheck }) => {
const [expanded, setExpanded] = useState(false);
const [children, setChildren] = useState([]);
const handleToggle = async () => {
if (!expanded && node.children === undefined) {
// 懒加载子节点
const res = await fetch(https://jztheme.com/api/tree/${node.id});
const data = await res.json();
setChildren(data);
}
setExpanded(!expanded);
onToggle?.(node, !expanded);
};
return (
<div style={{ paddingLeft: level * 20 }}>
<div onClick={handleToggle}>
{node.title}
{expanded && (
<div>
{children.map(child => (
<TreeNode
key={child.id}
node={child}
level={level + 1}
onToggle={onToggle}
onCheck={onCheck}
/>
))}
</div>
)}
</div>
</div>
);
};
这种写法虽然啰嗦点,但内存占用低、响应快。3000 个节点渲染时间从 800ms 降到 60ms,用户终于不骂了。当然,代价是得自己处理 checkbox 的父子联动、搜索高亮、拖拽排序这些功能——但至少性能稳了。
我的选型逻辑
我现在的选型规则很粗暴:
- 节点数 < 200,交互简单 → 直接用 Ant Design / Element Plus。省下的时间能多喝两杯咖啡。
- 节点数 > 500,或需要复杂交互(如拖拽、实时搜索) → 自己写递归 + 虚拟滚动。初期多花两天,后期少背锅。
- 必须支持 SSR(服务端渲染) → 小心 Ant Design 的 Tree,它在 Node.js 环境下可能报 window 未定义的错,得加动态 import。自己写的反而可控。
这里注意我踩过好几次坑:有些团队为了“统一技术栈”,硬在 Vue 项目里用 Ant Design Vue 的 Tree,结果发现它的异步加载 API 和 React 版本不一致,文档还少,最后还得自己封装一层。不如直接用 Element Plus,或者干脆手写。
踩坑提醒:这三点一定注意
1. **key 的稳定性**:千万别用数组索引当 key!我见过有人写 key={index},结果节点展开后顺序乱掉,checkbox 状态全错。一定要用唯一 ID,比如 key={node.id}。
2. **异步加载的防抖**:用户狂点展开按钮怎么办?加个 loading 状态 + 请求取消(AbortController):
const [loading, setLoading] = useState(false);
const abortRef = useRef(null);
const loadChildren = async (parentId) => {
if (abortRef.current) abortRef.current.abort();
abortRef.current = new AbortController();
try {
setLoading(true);
const res = await fetch(/api/children/${parentId}, {
signal: abortRef.current.signal
});
// ...
} finally {
setLoading(false);
}
};
3. **虚拟滚动的 height 固定**:用 react-window 时,每个节点高度必须固定,否则滚动位置会跳。如果设计稿里节点高度可变(比如带多行描述),要么说服设计师改,要么用 VariableSizeList——但性能会打折扣,慎用。
结语
以上是我个人对 Tree 组件的选型和实战总结。没有银弹,只有权衡。Ant Design 适合快速交付,自研方案扛得住复杂场景。如果你的数据量不大,别瞎折腾;如果已经卡到用户投诉,那就别犹豫,该重写就重写。
以上是我踩坑后的总结,希望对你有帮助。有不同看法欢迎评论区交流,比如你们用什么方案处理万级节点的 Tree?

暂无评论