React组件Props未变更却多次重渲染问题求助
看起来你遇到的核心问题是React.memo的包裹顺序错误,加上旧版本React-Redux的浅比较机制,再配合Firebase订阅触发的多次store更新,共同导致了不必要的重渲染。让我们一步步拆解:
1. React.memo的使用顺序完全搞反了
你现在的写法是:
export default React.memo(connect(mapStateToProps, mapDispatchToProps)(Sidebar), areEqual)
这里React.memo包裹的是connect返回的高阶组件,而不是你的原始Sidebar组件。这意味着areEqual比较的是父组件(AppRouter)传递给Sidebar的props(这里是空对象{}),而不是connect从store获取并传递给Sidebar的props。
当store更新时,connect会触发内部的重渲染逻辑,这时候memo根本管不到——它只关心父组件有没有传新的props,而完全忽略store带来的props变化。
修正方式:
把memo包裹在原始组件上,再传给connect:
const MemoizedSidebar = React.memo(Sidebar, areEqual); export default connect(mapStateToProps, mapDispatchToProps)(MemoizedSidebar);
这样areEqual才会真正比较connect从store拿到的props,当内容相同时阻止重渲染。
2. React-Redux 3.7.2的默认浅比较限制
你的React-Redux版本相当老旧(当前稳定版是8.x),它的connect默认使用浅比较来判断props是否变化。如果你在mapStateToProps里每次都返回一个新对象(比如return { user: state.user }),即使对象内容完全相同,浅比较也会认为props变了,从而触发重渲染。
虽然你想用React.memo + _.isEqual做深比较,但因为顺序错了,这个逻辑根本没生效。
替代方案(不需要React.memo):
直接给connect传入配置项,用深比较替代默认的浅比较:
import _ from 'lodash'; const connectOptions = { // 比较从store获取的props是否相同 areStatePropsEqual: (prevProps, nextProps) => _.isEqual(prevProps, nextProps), // 如果需要,也可以合并比较所有props(stateProps + 父组件props + dispatchProps) // areMergedPropsEqual: (prevProps, nextProps) => _.isEqual(prevProps, nextProps) }; export default connect(mapStateToProps, mapDispatchToProps, null, connectOptions)(Sidebar);
这样connect内部会直接用深比较判断是否需要重渲染,不需要额外的React.memo。
3. Firebase订阅可能触发了不必要的store更新
你的控制台里多次出现Sidebar mapStateToProps,说明store在短时间内多次收到dispatch。这很可能是Firebase的实时订阅回调被多次触发——比如初始加载时的重复回调,或者数据库有频繁的微小变化(比如元数据更新)。
优化方式:
在Firebase的订阅回调里加一层判断,只有当数据真的变化时才dispatch:
let lastFetchedData = null; firebase.database().ref('your-target-path').on('value', (snapshot) => { const newData = snapshot.val(); // 用深比较判断数据是否真的变化 if (!_.isEqual(newData, lastFetchedData)) { lastFetchedData = newData; dispatch(updateYourDataAction(newData)); } });
这样能避免很多无意义的store更新,从根源减少重渲染。
额外检查:AppRouter的重渲染
你的AppRouter也被connect包裹了,如果它的mapStateToProps返回的props频繁变化,会导致AppRouter重渲染。虽然你给Sidebar加了memo,但如果顺序错了,还是会触发Sidebar的重渲染。可以给AppRouter也加上类似的深比较逻辑,或者检查它的mapStateToProps是否返回了不必要的新对象。
内容的提问来源于stack exchange,提问作者Kermit

