求助: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}: <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
相关产品推荐
相关产品推荐

