You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何防止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逻辑后问题消失,说明这个不是核心诱因。
解决方案

按优先级依次修复即可:

  1. 修正初始loading状态,避免首屏错误渲染路由
    将auth slice初始状态的loading改为true,让首屏直接进入加载状态,等用户信息请求完成后再挂载路由区域,从根源避免Ips组件被先挂载再卸载的问题:
const initialState: UserModel = {
    user_id: null,
    email: '',
    name: '',
    isLoggedIn: false,
    loading: true, // 初始值改为true,首屏直接走用户信息加载逻辑
    error: false,
}
  1. 修复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)
  1. (可选)子组件渲染优化
    对于不依赖父组件频繁变化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只能优化普通重渲染问题,解决不了条件渲染导致的组件卸载重载,必须先完成前两步修复。

  1. (可选)简化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 23:12:59