使用normalizr处理Redux数据后React组件不必要重渲染问题求解
React嵌套层级不必要重渲染解决方案
核心原因
你将完整的grandChildrenDetails、childrenDetails大对象逐层透传,当孙组件数据更新时,对应集合的引用发生变化,React.memo默认浅比较会判定props变化,触发所有收到该集合的上层组件重渲染。
优化方案
方案1:组件独立订阅所需数据(优先级最高)
取消跨层级透传完整数据集合,每个组件只接收对应id,内部从Redux订阅自身需要的最小粒度数据,从根源避免上层组件收到无关数据变化的影响。
代码示例:
// 孙组件:仅接收id,独立拉取自身数据 const GrandChildren = React.memo(({ grandChildId }) => { const grandChild = useSelector(state => state.grandChildrenDetails[grandChildId]); return <div>{grandChild.name} - {grandChild.age}</div>; }); // 子组件:仅接收id,独立拉取自身数据,仅透传孙组件id const Children = React.memo(({ childId }) => { const child = useSelector(state => state.childrenDetails[childId]); return ( <div> <p>{child.name}</p> {child.grandChildren.map(id => <GrandChildren key={id} grandChildId={id} />)} </div> ); }); // 父组件:仅接收id,独立拉取自身数据,仅透传子组件id const Parent = React.memo(({ parentId }) => { const parent = useSelector(state => state.parentDetails[parentId]); return ( <div> <p>{parent.name}</p> {parent.children.map(id => <Children key={id} childId={id} />)} </div> ); }); // 根组件:仅遍历parentId列表,透传父组件id const Base = () => { const parentIds = useSelector(state => state.parentIds); return parentIds.map(id => <Parent key={id} parentId={id} />); };
这种方案下,更新某条孙组件数据时,只有对应id的孙组件会被触发重渲染,上层组件完全不受影响。
方案2:为React.memo添加自定义比较逻辑
如果暂时不想调整数据传递逻辑,可以给上层组件的memo添加自定义比较函数,忽略组件实际未使用的透传大对象,只比较组件渲染依赖的属性。
代码示例:
const Parent = React.memo(({ parentObject, childrenDetails, grandChildrenDetails }) => { // 原有组件逻辑 }, (prevProps, nextProps) => { // 只比较组件渲染实际依赖的属性,跳过透传的大对象比较 return prevProps.parentObject.id === nextProps.parentObject.id && JSON.stringify(prevProps.parentObject.children) === JSON.stringify(nextProps.parentObject.children); });
注意:自定义比较逻辑不要过于复杂,否则比较耗时会超过重渲染的性能损耗,反而得不偿失。
方案3:保证Redux更新的结构化共享
更新Redux数据时使用Immutable更新逻辑,只修改发生变化的条目的引用,未变化的集合、条目保持原有引用,即使透传完整集合,浅比较也能正常通过。推荐使用Immer库简化Immutable更新逻辑:
import { produce } from 'immer'; const reducer = produce((draft, action) => { if (action.type === 'UPDATE_GRAND_CHILD') { const { id, newAge } = action.payload; // 仅修改对应孙组件的条目引用,grandChildrenDetails本身、其他孙组件条目、父/子集合的引用全部保持不变 draft.grandChildrenDetails[id].age = newAge; } }, initialState);
内容的提问来源于stack exchange,提问作者A J
相关产品推荐
相关产品推荐

