You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Material UI Tree View:点击节点时仅触发叶子节点函数的优化实现方案咨询

解决Material UI TreeView仅触发叶子节点点击事件的高效方案

嘿,我之前在处理大体积树形数据的时候也遇到过这个问题,遍历整个JSON确实太影响性能了,这里有几个更高效的优化方案,你可以根据自己的场景选:

方案一:预构建ID-节点映射表(推荐)

既然你的数据是固定的(如果是动态加载的也可以在数据更新时重新构建),我们可以在组件初始化时用useMemo一次性生成一个ID到节点对象的映射,这样后续点击节点时直接通过ID就能O(1)拿到完整节点信息,完全不用遍历。

修改你的代码如下:

const data = {
  id: 'root',
  name: 'Parent',
  children: [
    { id: '1', name: 'Child - 1', },
    { id: '3', name: 'Child - 3', children: [
      { id: '4', name: 'Child - 4', },
    ], },
  ],
};

const useStyles = makeStyles({
  root: {
    height: 110,
    flexGrow: 1,
    maxWidth: 400,
  },
});

// 递归构建ID-节点映射的工具函数
const buildNodeMap = (nodes, map = new Map()) => {
  map.set(nodes.id, nodes);
  if (Array.isArray(nodes.children)) {
    nodes.children.forEach(node => buildNodeMap(node, map));
  }
  return map;
};

export default function RecursiveTreeView() {
  const classes = useStyles();
  // 仅在data变化时重新构建映射
  const nodeMap = useMemo(() => buildNodeMap(data), [data]);

  const handleNodeSelect = (event, nodeId) => {
    const node = nodeMap.get(nodeId);
    // 判断是否为叶子节点(没有children或者children为空数组)
    if (!node.children || node.children.length === 0) {
      // 这里执行你的特定函数
      console.log('触发叶子节点逻辑:', node);
    }
  };

  const renderTree = (nodes) => (
    <TreeItem key={nodes.id} nodeId={nodes.id} label={nodes.name}>
      {Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null}
    </TreeItem>
  );

  return (
    <TreeView 
      className={classes.root} 
      defaultCollapseIcon={<ExpandMoreIcon />} 
      defaultExpanded={['root']} 
      defaultExpandIcon={<ChevronRightIcon />}
      onNodeSelect={handleNodeSelect}
    >
      {renderTree(data)}
    </TreeView>
  );
}

这个方法的优势是:映射只构建一次(除非data更新),查询节点的时间复杂度是O(1),完全不会因为数据量大而变慢。

方案二:在渲染TreeItem时直接绑定节点点击逻辑

既然renderTree函数本身就拿到了每个节点的完整数据,我们可以直接给每个TreeItem添加自定义的点击事件,跳过TreeView的全局onNodeSelect,这样点击时直接就能判断当前节点是否为叶子节点:

// ... 其他代码不变

export default function RecursiveTreeView() {
  const classes = useStyles();

  const handleLeafNodeClick = (node) => {
    // 这里执行你的特定函数
    console.log('触发叶子节点逻辑:', node);
  };

  const renderTree = (nodes) => {
    const isLeaf = !nodes.children || nodes.children.length === 0;
    return (
      <TreeItem 
        key={nodes.id} 
        nodeId={nodes.id} 
        label={nodes.name}
        onClick={() => {
          if (isLeaf) {
            handleLeafNodeClick(nodes);
          }
        }}
      >
        {Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null}
      </TreeItem>
    );
  };

  return (
    <TreeView 
      className={classes.root} 
      defaultCollapseIcon={<ExpandMoreIcon />} 
      defaultExpanded={['root']} 
      defaultExpandIcon={<ChevronRightIcon />}
    >
      {renderTree(data)}
    </TreeView>
  );
}

这个方法的好处是逻辑更直接,不需要额外维护映射表,而且亲测不会和TreeView的展开/折叠事件冲突——展开折叠是点击图标触发的,节点标签的点击会优先触发我们的自定义事件。

方案三:利用自定义数据属性传递节点信息

我们可以在渲染TreeItem时,把节点数据序列化为JSON字符串存在data-*属性里,然后在TreeView的onNodeSelect事件中通过DOM元素获取这个属性并解析:

// ... 其他代码不变

export default function RecursiveTreeView() {
  const classes = useStyles();

  const handleNodeSelect = (event, nodeId) => {
    // 找到对应的TreeItem元素
    const treeItem = event.currentTarget.querySelector(`[node-id="${nodeId}"]`);
    if (!treeItem) return;
    // 解析data-node属性
    const node = JSON.parse(treeItem.getAttribute('data-node'));
    if (!node.children || node.children.length === 0) {
      // 执行特定函数
      console.log('触发叶子节点逻辑:', node);
    }
  };

  const renderTree = (nodes) => (
    <TreeItem 
      key={nodes.id} 
      nodeId={nodes.id} 
      label={nodes.name}
      data-node={JSON.stringify(nodes)}
    >
      {Array.isArray(nodes.children) ? nodes.children.map((node) => renderTree(node)) : null}
    </TreeItem>
  );

  return (
    <TreeView 
      className={classes.root} 
      defaultCollapseIcon={<ExpandMoreIcon />} 
      defaultExpanded={['root']} 
      defaultExpandIcon={<ChevronRightIcon />}
      onNodeSelect={handleNodeSelect}
    >
      {renderTree(data)}
    </TreeView>
  );
}

这个方法适合不想修改太多原有逻辑的场景,不过要注意如果节点数据里有特殊字符,JSON.stringify可能会有小问题,一般树形数据不会遇到这种情况。

内容的提问来源于stack exchange,提问作者Tenshi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 20:52:51