React组件useEffect内调用useDispatch引发重复渲染的解决方法
React组件重复渲染问题排查与解决
问题场景
在React项目中,子组件Dashboard的useEffect(对应componentDidMount阶段)调用useDispatch触发changeSalesDashboardData action时,组件出现持续重复渲染。父组件SalesReports仅通过useSelector获取state.reports.selectedSalesTab,并未使用salesDashBoardDatta数据,移除该dispatch操作后组件运行恢复正常。
根本原因
核心问题出在父组件的useSelector写法,以及由此引发的连锁反应:
- 父组件中
useSelector每次都会返回一个全新的对象:
只要redux状态发生任何变化(包括const { selectedSalesTab } = useSelector<RootState, any>((state: RootState) => { return { selectedSalesTab: state.reports.selectedSalesTab, }; });salesDashBoardDatta更新),这个匿名函数都会生成新对象,导致useSelector判定状态变更,触发父组件SalesReports重新渲染。 - 父组件重复渲染时,即便
getPageContent用了useMemo,但Dashboard组件没有被稳定缓存,会被反复卸载再挂载,进而重复执行useEffect里的请求和dispatch,形成循环:Dashboard挂载 → 发起请求 → dispatch更新salesDashBoardDatta- redux状态变化 → 父组件
useSelector返回新对象 → 父组件重渲染 - 父组件重渲染 →
Dashboard被重建(卸载再挂载) - 回到第一步,循环往复
解决方案
1. 优化useSelector写法,避免无意义更新
直接返回单个值,而非包装成对象,这样只有当selectedSalesTab真正变化时才会触发组件更新:
// 父组件SalesReports修改useSelector const selectedSalesTab = useSelector<RootState, string>( (state: RootState) => state.reports.selectedSalesTab );
如果需要同时获取多个状态值,使用shallowEqual比较器做浅比较,避免因新对象导致的不必要更新:
import { shallowEqual, useSelector } from 'react-redux'; const { selectedSalesTab } = useSelector<RootState, any>( (state: RootState) => ({ selectedSalesTab: state.reports.selectedSalesTab, }), shallowEqual // 仅当属性值真的变化时,才判定状态更新 );
2. 给Dashboard组件添加缓存,防止重复重建
用React.memo包装Dashboard,避免父组件重渲染时无意义地重建子组件:
// Dashboard组件导出前添加包装 import React from 'react'; const Dashboard: FC = () => { // 组件逻辑不变 }; export default React.memo(Dashboard);
3. 规范useEffect依赖项,避免意外重复执行
给useEffect补充完整依赖项,同时清理RxJS订阅防止内存泄漏:
// Dashboard组件的useEffect修改 const repostsRxjs = useMemo(() => rxjsConfig(reportingAxios), [reportingAxios]); // 缓存repostsRxjs,避免每次渲染生成新对象 const dispatch = useDispatch(); useEffect(() => { const subscription = repostsRxjs .post<SalesDashboardItem[]>( '/sales-data/order-details/6087bc3606ff073930a10848?timezone=Asia/Dubai&from=2022-09-03T00:00:00.00Z&to=2022-12-25T00:00:00.00Z&size=10', { brandIds: [], channelIds: [], kitchenIds: [], countryIds: [], }, ) .pipe( take(1), catchError((err: any) => { console.log(err); return of(); }), ) .subscribe((response: SalesDashboardItem[] | void) => { if (response) { dispatch(reportsActionCreators.changeSalesDashboardData(response)); } }); // 组件卸载时清理订阅 return () => subscription.unsubscribe(); }, [dispatch, repostsRxjs]); // 显式声明依赖项,确保逻辑稳定
内容的提问来源于stack exchange,提问作者supun rajasinghe
相关产品推荐
相关产品推荐

