如何防止Redux dispatch触发子组件不必要的重复重渲染
问题根因
你遇到的重复执行问题本质不是普通的组件重渲染,是组件被反复卸载+重新挂载+多余重渲染共同导致的,核心有两个问题:
- 初始状态配置错误导致的条件渲染循环
你的auth slice初始loading值设为了false,App首次渲染时会直接挂载Layout下的路由和Ips组件,触发第一次useEffect执行。紧接着fetchUser的pending action将loading改为true,App会直接卸载整个路由区域,渲染全局加载组件;等接口请求完成loading改回false,又会重新挂载Ips组件,触发第二次useEffect执行。 useSelector写法错误触发多余重渲染
你当前的selector每次执行都会返回一个全新的数组引用,Redux默认通过引用相等判断状态是否变更,哪怕loading和error的值没有变化,只要selector返回新数组,就会触发App组件重渲染,平白增加性能消耗。- 额外开发环境影响:如果你的应用入口包裹了
<React.StrictMode>,开发环境下组件首次挂载时会故意执行两次空依赖的useEffect做副作用校验,但你注释pending逻辑后问题消失,说明这个不是核心诱因。
解决方案
按优先级依次修复即可:
- 修正初始loading状态,避免首屏错误渲染路由
将auth slice初始状态的loading改为true,让首屏直接进入加载状态,等用户信息请求完成后再挂载路由区域,从根源避免Ips组件被先挂载再卸载的问题:
const initialState: UserModel = { user_id: null, email: '', name: '', isLoggedIn: false, loading: true, // 初始值改为true,首屏直接走用户信息加载逻辑 error: false, }
- 修复useSelector写法,消除多余重渲染
不要在selector里返回每次新建的数组/对象,两种改法选其一即可:
- 写法一:单独拆分每个状态的读取,不会产生新引用
// 删掉之前返回数组的写法,分开取值 const loading = useAppSelector((state: any) => state.auth.loading) const error = useAppSelector((state: any) => state.auth.error)
- 写法二:如果需要一次性取多个值,引入
shallowEqual做浅比较
// 先从react-redux导入shallowEqual import { useDispatch, shallowEqual } from "react-redux"; // 传入shallowEqual作为useSelector的第三个参数 const [loading, error] = useAppSelector((state: any) => [ state.auth.loading, state.auth.error, ], shallowEqual)
- (可选)子组件渲染优化
对于不依赖父组件频繁变化props的子组件,可以用React.memo包裹,仅当props实际变化时才触发重渲染:
import React, { useEffect } from 'react' const Ips = React.memo(function Ips() { useEffect(() => { alert('test') }, []) return ( <div className={classes.Ip}> test </div> ); }) export default Ips;
注意:
React.memo只能优化普通重渲染问题,解决不了条件渲染导致的组件卸载重载,必须先完成前两步修复。
- (可选)简化slice状态更新逻辑
Redux Toolkit内置了Immer支持,不需要每次更新状态都拷贝整个state对象,直接修改属性即可,减少冗余代码和出错概率:
extraReducers: builder => { builder .addCase(fetchUser.pending, (state) => { state.loading = true state.error = false }) .addCase(fetchUser.fulfilled, (state, action) => { Object.assign(state, action.payload.data, { isLoggedIn: true, loading: false, error: false }) }) .addCase(fetchUser.rejected, (state, action) => { state.loading = false state.error = action.error?.message || true }); }
内容的提问来源于stack exchange,提问作者Mohammad A. Souri
相关产品推荐
相关产品推荐

