使用React.memo、useMemo、useCallback避免对象传参引发的子组件意外重渲染
问题根因
React.memo默认对props做浅比较,仅判断props的内存引用是否相等。你的代码中App组件每次重渲染时,都会在函数体内重新创建sourceData对象,哪怕对象内容完全一致,它的引用也是全新的,因此传给TextExample3的data prop每次都无法通过浅比较,触发子组件意外重渲染。
优雅解决方案
下面两种方案都可以替代JSON序列化的hack实现:
方案1:给React.memo传入自定义比较函数
不用修改父组件代码,直接在子组件侧自定义props的比较规则:
// 基础写法,按属性逐个比较 const TextExample3 = React.memo(({data, infoText}) => { console.log(infoText); return ( <p> {infoText} {data.name} {data.surname} </p> ); }, (prevProps, nextProps) => { // 返回true表示props无变化,不需要重渲染 return prevProps.infoText === nextProps.infoText && prevProps.data.name === nextProps.data.name && prevProps.data.surname === nextProps.data.surname });
如果对象层级深,可以直接用深比较工具简化代码:
import isEqual from 'lodash/isEqual'; const TextExample3 = React.memo(({data, infoText}) => { // 组件原有逻辑不变 }, (prevProps, nextProps) => isEqual(prevProps, nextProps));
注意:自定义比较函数的返回值和shouldComponentUpdate相反,返回true代表跳过重渲染,返回false代表触发重渲染。
方案2:父组件用useMemo缓存对象引用
不用修改子组件代码,在父组件侧保证对象引用稳定,默认的浅比较就能生效:
const App = () => { console.log('----- NEW RENDER -----'); const [forceRerenderNumber, setForceRerenderNumber] = useState(0); // 用useMemo缓存对象,依赖项为空时仅会在首次渲染时创建一次 const sourceData = React.useMemo(() => ({ name: 'Joe', surname: 'Moore', }), []); // 剩余原有代码不变 };
如果sourceData的内容依赖其他state/prop,只需要把对应变量加到useMemo的依赖数组中即可,只有依赖变化时才会生成新的对象。
方案选型建议
- 如果子组件复用性高,接收的对象结构不固定:优先选方案2,父组件自行缓存传参,子组件不需要兼容不同结构的比较逻辑
- 如果子组件固定接收特定结构的参数:优先选方案1,父组件不需要额外做缓存处理
- 如果对象层级极深、数据量极大:优先选方案2,深比较的开销可能远高于子组件重渲染的开销,缓存引用的性能收益更高
你之前尝试useMemo没生效,大概率是没有把对象的创建逻辑包在useMemo中,或是依赖数组填写错误导致的。
内容的提问来源于stack exchange,提问作者CodeJoe
相关产品推荐
相关产品推荐

