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

求助:Redux状态更新后React组件未重新渲染

Redux状态更新后React组件不重新渲染排查

我是React和Redux新手,这个问题卡了好几天:Redux状态已通过DevTools确认更新,但ComponentTwo完全不重新渲染。ComponentOne能按初始Redux状态渲染,提交后Redux状态更新,但ComponentTwo只有自身props变化时才会更新。忽略ComponentOne混用本地状态的问题,后续会改,代码已简化不影响问题。

技术栈

  • TypeScript
  • React
  • Redux

项目结构

|__src
|  |__components
|  |  |__ComponentOne.tsx - 触发Redux dispatch,使用useDispatch
|  |  |__ComponentTwo.tsx - 不重新渲染的组件,使用useSelector
|  |__functions
|  |  |__functions.ts
|  |__redux
|  |  |__store.ts
|  |  |__actionsMaster.ts
|  |  |__reducers
|  |  |  |__quarterReducer.ts
|  |  |  |__filingStatusReducer.ts
|  |  |  |__rootReducer.ts

ComponentOne.tsx

import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { changeQuarter, changeFilingStatus } from "../redux/action-creators/actionsMaster";
import { constantsOne } from "../constants";
import { RootState } from "../redux/store";

const ComponentOne= () => {

const quarter = useSelector<RootState, string>(state => state.quarter);
const filingStatus = useSelector<RootState, string>(state => state.filingStatus);
const dispatch = useDispatch();

const [ quarterState, setQuarterState ] = useState("1");
const [ filingStatusState, setFilingStatusState ] = useState("SINGLE");

const handleSubmit = () => {
  let tempQuarter = quarterState;
  let tempFilingStatus = filingStatusState;

try {
  dispatch(changeQuarter(tempQuarter));
  dispatch(changeFilingStatus(tempFilingStatus));
  setQuarterState(tempQuarter);
  setFilingStatusState(tempFilingStatus);
     }

catch(err) {
  console.log(err);
     }
 };

const handleChange = (elementId: number, e: { target: HTMLSelectElement }) => {
 let tempValue = e.target.value;

 // Each case goes in order from the constantsOne Element value from constants. If you break the order, the function will not perform properly.
switch(elementId){
 case 0:
  setQuarterState(tempValue);
  break;
 case 1:
  setFilingStatusState(tempValue);
  break;  
 default:
  console.log("No case was selected");    
     };
 };

return ( 
         <div>
         <div id="component1-box">
         <div className="container-fluid">
            <div className="box-title">COMPONENT 1 TITLE</div>
            <div className="d-flex align-content-around flex-wrap justify-content-start">
               {constantsOne.map((item) =>
                <div className="p-3 component1-title" key={item.id}>{item.element}:&nbsp; 
                 <select name={item.element} className="component1-select" id="component1-item" onChange={(e) => handleChange(item.id, e)}>
                 {item.array.map((value) => 
                     <option key={value.id} value={value.arrayValue}>{value.arrayValue}</option>
                 )};
                 </select> 
               </div>
                )}
            </div>
             <button id="component1-submit-button" className="submit-button" type="submit" onClick={handleSubmit}>SUBMIT</button><br/>
         </div>
        </div>
        </div>
      );
 }

export default ComponentOne;

ComponentTwo.tsx

import functionOne from "../functions/functionOne";
 import functionTwo from "../functions/functionTwo";
 import functionThree from "../functions/functionThree";
 import { useSelector } from "react-redux";
 import { 
  modelOneType,
  modelTwoType,
  modelThreeType
 } from "../types";
 import { 
  constantsTwo, 
  constantsThree
 } from "../constants";
 import { RootState } from "../redux/store";
 
 interface componentTwoProps {
  props1: modelOneType,
  props2: modelTwoType,
  props3: modelThreeType
 }
 
 const ComponentTwo= ({ 
  props1,
  props2, 
  props3
 }: componentTwoProps 
 ) => {
 
 const quarterFromStore = useSelector<RootState, string>(state => state.quarter);
 const filingStatusFromStore = useSelector<RootState, string>(state => state.filingStatus);
 
 const valueOne: number = functionOne(quarterFromStore);
 const valueTwo: modelOneType = functionTwo(props1, props2, valueOne);
 const valueThree: number = functionThree(valueTwo, constantsTwo);
 
 // There are many more consts that have new data based off of other consts. I excluded because I do not think it impacts the problem at hand. There are really up to like valueThirty.
 // the div below has also been simplified but should not impact issue at hand

 return (
         <div id="numbers-output-with-tax">
             <header className="box-title">COMPONENT TWO TITLE</header>
           <table className="table">
                 <thead className="table-header">
                    <tr>
                       <th scope="col">DESCRIPTION</th>
                        <th scope="col">COLUMN ONE</th>
                    </tr>
                 </thead>
                <tbody>
                        {constantsTwo.map((item) => 
                       <tr className="table-row-item-regular" key={item.id}>
                            <th scope="row" className="table-description-item">{item.element}</th>
                          <td>{valueTwo[item.hardValue as keyof modelOneType]}</td>
                         </tr>
                       )}

                         <tr className="table-total-row">
                            <th scope="row">TOTAL</th>
                            <td>{valueThree}</td>
                        </tr>
 
                       {constantsThree.map((item) => 
                       <tr className="table-row-item-regular" key={item.id}>
                            <th scope="row" className="table-description-item">{item.element}</th>
                            <td>{valueTwo[item.hardValue as keyof modelOneType]}</td>
                        </tr>
                        )}
                 </tbody>
            </table>
         </div>
      );
 }

export default ComponentTwo;

App.tsx

import Header from './components/Header'
import UserTitleBox from './components/UserTitleBox';
import ComponentOne from './components/ComponentOne';
import InputComponent from './components/InputComponent';
import Notes from './components/Notes';
import Contact from './components/Contact';
import Footer from './components/Footer'


function App() {
  return (
    <div className="App">
         <Header/>
         <UserTitleBox />
         <ComponentOne/>
         <InputComponent />

        // ComponentTwo is a child of InputComponent

        <Notes />
        <Contact />
         <Footer />
     </div>
  );
}

export default App;

store.ts

import rootReducer from "./reducers/rootReducer";
import { configureStore } from "@reduxjs/toolkit";

export const store = configureStore({
    reducer: rootReducer
});

export type RootState = ReturnType<typeof store.getState>;

actionsMaster.ts

import { 
    modelOneType, 
    modelTwoType,
    modelThreeType
} from "../../types";
import { Dispatch } from 'redux';
import { 
    CHANGEQUARTER, 
    CHANGEFILINGSTATUS,
    CHANGENUMBERSINPUT,
    CHANGENUMBERSANNUALIZATION,
    CHANGEPAYMENTSINPUT
 } from '../constants';

export const changeQuarter = (quarter: string) => {
    return (dispatch: Dispatch) => {
        dispatch({
            type: CHANGEQUARTER,
            payload: quarter
        })
    }
};

export const changeFilingStatus = (filingStatus: string) => {
    return (dispatch: Dispatch) => {
        dispatch({
            type: CHANGEFILINGSTATUS,
            payload: filingStatus
        })
    }
};

export const changeNumbersInput = (numbersInputValues: modelOneType) =>{
    return (dispatch: Dispatch) => {
        dispatch({
            type: CHANGENUMBERSINPUT,
            payload: numbersInputValues
        })
    }
};

export const changeAnnualization = (numbersAnnualizationValues: modelTwoType) =>{
    return (dispatch: Dispatch) => {
        dispatch({
            type: CHANGENUMBERSANNUALIZATION,
            payload: numbersAnnualizationValues
        })
    }
};

export const changePaymentsInput = (paymentsInputValues: modelThreeType) =>{
    return (dispatch: Dispatch) => {
        dispatch({
            type: CHANGEPAYMENTSINPUT,
            payload: paymentsInputValues
        })
    }
};

quarterReducer.ts

import { PayloadAction } from '@reduxjs/toolkit';
import { CHANGEQUARTER } from '../constants';

const initialState: string = "1";

const reducer = (state = initialState, action: PayloadAction<string>) => {
    switch(action.type){
        case CHANGEQUARTER:
            return action.payload;
        default:
            return state;
    };
}

export default reducer;

filingStatusReducer.ts

import { PayloadAction } from '@reduxjs/toolkit';
import { CHANGEFILINGSTATUS } from '../constants';

const initialState: string = "SINGLE";

const reducer = (state = initialState, action: PayloadAction<string>) => {
    switch(action.type){
        case CHANGEFILINGSTATUS:
            return action.payload;
        default:
            return state;
    };
}

export default reducer;

rootReducer.ts

import { combineReducers } from "redux";
import quarterReducer from "./quarterReducer";
import filingStatusReducer from "./filingStatusReducer";
import numbersInputReducer from "./numbersInputReducer";
import paymentsInputReducer from "./paymentsInputReducer";
import numbersAnnualizationReducer from "./numbersAnnualizationReducer";

const rootReducer = combineReducers({
    quarter: quarterReducer,
    filingStatus: filingStatusReducer,
    numbersInputValues: numbersInputReducer,
    paymentsInputValues: paymentsInputReducer,
    numbersAnnualizationValues: numbersAnnualizationReducer
});

export default rootReducer;

排查方向

  • 检查InputComponent的缓存逻辑:如果InputComponent用了React.memo但没把Redux相关状态纳入依赖列表,会导致ComponentTwo被缓存不更新。确认InputComponent是否正确传递props,或更新memo的依赖项。
  • 验证useSelector的触发情况:在ComponentTwo的useSelector中添加日志,确认状态更新时是否触发,返回值是否确实变化:
    const quarterFromStore = useSelector<RootState, string>(state => {
      console.log('当前quarter状态:', state.quarter);
      return state.quarter;
    });
    
  • 检查业务函数的纯函数性:确认functionOne/functionTwo/functionThree是纯函数,输入变化时返回新的引用(针对对象/数组类型),避免因引用不变导致React判断值未更新。
  • 确认Provider包裹范围:检查项目入口文件(如index.tsx)是否将整个App组件包裹在Redux的Provider中,确保ComponentTwo能访问到Redux store。
  • 二次验证reducer逻辑:虽然当前reducer处理的是值类型,但确认reducer没有返回原状态,确保每次状态更新都返回新值。

内容的提问来源于stack exchange,提问作者RustyCoder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 18:35:28