Ant Design Tree组件展开时渲染缓慢问题排查求助
Ant Design Tree 组件性能优化问题
我基于create-react-app开发了一个包含Ant Design Tree组件的EntityBrowser,状态用Redux管理,即将上线生产环境。
通过排查发现,Tree组件在treeData数据量较大时,每次展开都会重新渲染所有TreeNode,导致性能急剧下降;但现在仅少量节点就卡顿到无法使用,动态添加节点、展开操作都受影响,已通过性能分析确认节点数量/数据量增加会拉长渲染时间。
已尝试移除onSelect、titleRender等非必要props,提取内联函数,仅获得小幅性能提升,未解决核心卡顿问题。
Tree组件代码
<Tree rootClassName={dark ? "dark-tree" : ""} style={{ borderRadius: "0" }} blockNode={true} onExpand={onExpand} onSelect={onSelect} selectedKeys={selectedKeys} expandedKeys={expandedKeys} treeData={treeData} titleRender={TreeNode} />
onExpand和onSelect处理逻辑
const onExpand = (expandedKeys) => { dispatch(entityBrowserActions.updateExpandedKeys(expandedKeys)); }; const onSelect = (selectedKeys, { event }) => { if (handleSelection) handleSelection(selectedKeys); dispatch(entityBrowserActions.updateSelectedKeys(selectedKeys)); };
treeData生成函数
const createTreeData = (entityStructure) => { return entityStructure.map((item) => { const itemSelectable = !selectedKeys.includes(item.key); let title = (<span>{item.name}</span>); let itemObj = {}; let newName = (e) => onNameChange(e, item.key); let stopEdit = () => handleStopEdit(item.key); if (editable && editingEntities[item.key] == true) { title = ( <Input value={item.name} onChange={ newName } onBlur={ stopEdit } /> ); } if (item.children) { itemObj = { title, key: item.key, selectable: itemSelectable, // Selectable only if not already selected checkable: false, children: createTreeData(item.children), }; } else { itemObj = { title, key: item.key, selectable: itemSelectable, } } return itemObj; }); };
titleRender渲染函数
const TreeNode = (value, record) => { const handleEdit = () => handleStartEdit(value.key); const handleClose = () => handleClosePopup(value.key, deletePopoverVisible[value.key]); const handleDeleteAndClose = () => { handleDelete(value.key); handleClosePopup(value.key, false); } return ( <div className="tree-title"> {value.title} {editable ? ( <div style={{}}> <Button type="text" style={{ padding: "2px 4px" }} onClick={ handleEdit }> <EditOutlined style={{ fontSize: "1.1rem" }} /> </Button> <Popover visible={ deletePopoverVisible[value.key] } onVisibleChange={ handleClose } content={ <div style={{ display: "flex", flexDirection: "column" }}> <Button type="text" style={{ color: "red" }} onClick={ handleDeleteAndClose }> Delete </Button> <Button type="text" onClick={ handleClose }> Cancel </Button> </div> } trigger="click"> <Button type="text" style={{ padding: "2px 4px" }}> <DeleteOutlined style={{ fontSize: "1.1rem" }} /> </Button> </Popover> </div> ) : null} </div> ); };
针对性优化方案
1. 缓存treeData避免全量重生成
createTreeData每次渲染都会递归生成全新的treeData对象,且每个节点的title都是新的React元素,导致Tree无法复用现有节点,强制全量重渲染。
- 优化实现:用
useMemo缓存treeData,仅在依赖项变化时重新生成const treeData = useMemo(() => { const createTreeData = (entityStructure) => { // 原createTreeData逻辑不变 }; return createTreeData(entityStructure); }, [entityStructure, selectedKeys, editable, editingEntities, onNameChange, handleStopEdit]);
2. 缓存titleRender组件与事件函数
TreeNode每次渲染都会创建新的内联事件函数,返回的DOM结构每次都是新引用,导致节点频繁重渲染。
- 优化实现:
- 把
TreeNode提取为独立纯组件,用React.memo包裹 - 用
useCallback缓存内部事件处理函数
// 独立纯组件 const TreeTitleNode = React.memo(({ value, editable, handleStartEdit, handleClosePopup, deletePopoverVisible, handleDelete }) => { const handleEdit = useCallback(() => handleStartEdit(value.key), [handleStartEdit, value.key]); const handleClose = useCallback(() => handleClosePopup(value.key, deletePopoverVisible[value.key]), [handleClosePopup, value.key, deletePopoverVisible]); const handleDeleteAndClose = useCallback(() => { handleDelete(value.key); handleClosePopup(value.key, false); }, [handleDelete, handleClosePopup, value.key]); return ( <div className="tree-title"> {value.title} {editable ? ( <div> <Button type="text" style={{ padding: "2px 4px" }} onClick={handleEdit}> <EditOutlined style={{ fontSize: "1.1rem" }} /> </Button> <Popover visible={deletePopoverVisible[value.key]} onVisibleChange={handleClose} content={ <div style={{ display: "flex", flexDirection: "column" }}> <Button type="text" style={{ color: "red" }} onClick={handleDeleteAndClose}> Delete </Button> <Button type="text" onClick={handleClose}> Cancel </Button> </div> } trigger="click"> <Button type="text" style={{ padding: "2px 4px" }}> <DeleteOutlined style={{ fontSize: "1.1rem" }} /> </Button> </Popover> </div> ) : null} </div> ); }); // 原组件中使用缓存后的函数 const TreeNode = useCallback((value, record) => { return <TreeTitleNode value={value} editable={editable} handleStartEdit={handleStartEdit} handleClosePopup={handleClosePopup} deletePopoverVisible={deletePopoverVisible} handleDelete={handleDelete} />; }, [editable, handleStartEdit, handleClosePopup, deletePopoverVisible, handleDelete]); - 把
3. 优化Redux状态更新逻辑
onExpand和onSelect直接dispatch新数组,可能导致组件因引用变化触发不必要重渲染。
- 优化实现:在reducer中仅当内容变化时更新状态
case UPDATE_EXPANDED_KEYS: const newExpandedKeys = action.payload; // 用lodash.isEqual或自定义函数比较数组内容 if (JSON.stringify(state.expandedKeys) === JSON.stringify(newExpandedKeys)) { return state; } return { ...state, expandedKeys: newExpandedKeys };
4. 启用虚拟滚动(核心优化)
Ant Design Tree支持虚拟滚动,仅渲染可视区域内的节点,大幅减少渲染压力。
- 优化实现:给Tree组件添加
virtual属性并设置高度<Tree // 其他属性不变 virtual={true} style={{ height: 600, overflow: 'auto' }} />
5. 减少selectable的重计算触发
selectedKeys.includes(item.key)会让所有节点依赖整个selectedKeys数组,数组变化时所有节点都会重计算selectable。
- 优化实现:把
selectable判断移到TreeTitleNode内部,或用useMemo缓存单个节点的选中状态判断结果。
内容的提问来源于stack exchange,提问作者Mads Garff
相关产品推荐
相关产品推荐

