React:如何避免14k节点大型树形结构的子组件不必要重渲染
问题根源
所有TreeTableBranch组件都通过useSelector订阅了完整的expanded Set对象,Redux的useSelector默认通过引用全等判断返回值是否变化,每次更新expanded都会生成新的Set引用,导致所有订阅了该状态的组件全部触发重渲染,这是性能问题的核心原因。
之前的优化方案失效原因:
- 直接取完整
expanded对象的逻辑没有修改,组件本身已经被触发重渲染,useMemo仅能缓存子节点,无法阻止组件本身的渲染逻辑执行 - 自定义
React.memo的比较函数仅判断了不存在于props中的shouldExpand属性,没有对实际传入的props做校验,无法生效
解决方案
1. 修正useSelector的订阅逻辑
将单个节点的展开状态判断放在useSelector内部,仅返回布尔类型的当前节点展开状态,只有当当前节点的展开状态真实变化时,才会触发组件重渲染:
// 修正前:返回完整Set,引用变化就触发重渲染 const expanded = useSelector((state: AppState) => state.categories.expanded); const shouldExpand = expanded.has(node.category.id); // 修正后:仅返回当前节点的展开状态,布尔值全等判断,无变化就不触发重渲染 const shouldExpand = useSelector((state: AppState) => state.categories.expanded.has(node.category.id) );
注意保证Redux中
expanded的更新是不可变的,每次修改都返回新的Set实例,不要修改原Set。
2. 稳定回调函数引用
所有传入TreeTableBranch的回调函数(onExpand/onAddCategory/onDeleteCategory)都需要用useCallback包裹,保证引用稳定,避免因为props变化触发不必要的重渲染:
// 父组件中定义回调示例 const onExpand = useCallback((nodeId: string) => { dispatch(toggleExpand(nodeId)); }, [dispatch]);
3. 正确配置React.memo
用React.memo包裹TreeTableBranch组件,自定义比较函数校验所有传入props是否相等:
const areEqual = (prevProps: Props, nextProps: Props) => { // 假设节点数据为不可变结构,引用不变则内容不变 return prevProps.node === nextProps.node && prevProps.level === nextProps.level && prevProps.onExpand === nextProps.onExpand && prevProps.onAddCategory === nextProps.onAddCategory && prevProps.onDeleteCategory === nextProps.onDeleteCategory; }; const TreeTableBranch = React.memo<Props>(({ node, level, onExpand, onAddCategory, onDeleteCategory }) => { const shouldExpand = useSelector((state: AppState) => state.categories.expanded.has(node.category.id)); return ( <> {/* 原有节点渲染逻辑 */} <button onClick={() => onExpand(node.category.id)}>Expand</button> { shouldExpand && node.children.map((v,i) => ( <TreeTableBranch key={v.category.id} node={v} level={level + 1} onExpand={onExpand} onAddCategory={onAddCategory} onDeleteCategory={onDeleteCategory} /> ))} </> ) }, areEqual);
4. 可选终极优化:虚拟滚动
如果14k节点初次渲染压力仍然较大,可以引入虚拟滚动方案,仅渲染可视区域内的节点,不管总节点数多少,同时渲染的节点仅为几十到上百个,彻底解决大量节点渲染的性能问题。
内容的提问来源于stack exchange,提问作者Jerry Hu
相关产品推荐
相关产品推荐

