Tree树形组件开发实战与性能优化经验分享

程序员逸龙 组件 阅读 2,407
赞 18 收藏
二维码
手机扫码查看
反馈

为啥我要折腾 Tree 组件?

最近项目里又遇到树形结构的需求,用户权限配置、分类管理、文件目录……绕不开。但每次用 Tree 都像在“选老婆”——没有完美的,只有更合适的。市面上主流方案就那几个:Ant Design 的 Tree、Element Plus 的 Tree、自己手写递归组件,还有用虚拟滚动优化的高性能方案。我踩过坑、也省过事,今天就唠点实在的。

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 里给你回传 checkedKeyshalfCheckedKeys,省不少事。所以中小项目我首选 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?

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论