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
相关产品推荐
相关产品推荐

