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

Redux状态对象多次渲染后变为undefined问题求助

打字练习工具的Redux状态异常问题

我用React+Redux开发打字练习工具,拆分了三个slice:Test-text(输出测试文本)、Input-text(读取输入文本)、Result(包含accuracy、WPM、WPMaverage的对象)。在获取Result的Accuracy值时,组件出现多次渲染,值依次为100、99、99、undefined、NaN,怀疑和useEffect有关,但不确定具体原因。


相关代码

Result切片

import {createSlice} from "@reduxjs/toolkit"

const ResultSlice = createSlice({
    name:"Result",
    initialState: {
        Accuracy:100,
        WPM:40,
        WPMAverage:[]
    },
    reducers:{
        setAccuracy(state,action){
            return state.Accuracy-1;
        }
    }
})

export const ResultReducer = ResultSlice.reducer;
export const {setAccuracy} = ResultSlice.actions;

InputText切片

import { createSlice } from "@reduxjs/toolkit";

const InputTextSlice = createSlice({
    name:"InputText",
    initialState:[],
    reducers:{
        setValue(State,action){
            return action.payload;
        }
    }
})

export const InputTextReducer = InputTextSlice.reducer;
export const {setValue} = InputTextSlice.actions;

Store配置

import { configureStore } from "@reduxjs/toolkit";
import {TestTextReducer} from './slices/TestText';
import { InputTextReducer } from "./slices/InputText";
import { ResultReducer } from "./slices/Result";
import { setTest} from "./slices/TestText";
import { setValue } from "./slices/InputText";
import { setAccuracy } from "./slices/Result";

const store  = configureStore({
    reducer:{
        TestText:TestTextReducer,
        InputText:InputTextReducer,
        Result:ResultReducer
    }
});

export {store, setTest, setValue, setAccuracy};

TextBox组件

import React, { useEffect } from 'react';
import { useDispatch,useSelector } from 'react-redux';
import { setValue,setAccuracy } from '../store';

const TextBox = () => {
  
  const Test = useSelector((state)=>{
    return state.TestText;
  })

  const InputText = useSelector((state)=>{
    return state.InputText;
  })

  const Accuracy = useSelector((state)=>{
    console.log(state.Result);
    return  state.Result.Accuracy;
  })

  const dispatch = useDispatch();

  const handleChange=(e)=>{
      dispatch(setValue(e.target.value));
  }
 
  useEffect(()=>{
    handleMatch();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  },[handleChange])

  
 
  function handleMatch(){
    if(Test === InputText){
      console.log( "a cmoplete match");
        dispatch(setValue(""));
        return;
     } 
     if(Test.includes(InputText)){
      console.log("good going");
      return;
     }
     else{
      console.log(Accuracy);
      dispatch(setAccuracy());
      return;
     }
  }


  return (
    <div className='TextBox'>
        <h2>Lesson 1</h2>
        <input 
           type="text"
           className='text-input'
           value={Test}
           style={{backgroundColor:"rgba(55,90,127,255)"}}/>
        <input 
           type="Text" 
           className='text-input user' 
           onChange={handleChange}
           value={InputText}
           />
        <div className='performance'>
          <h4 className='Tags'>WPM:</h4>
          <h4 className='Tags'>Accuracy:</h4>
          <h4 className='Tags'>Average WPM:</h4>
        </div>
    </div>
  )
}

export default TextBox;

问题原因分析

  1. Result切片状态结构被破坏
    setAccuracy reducer中直接返回state.Accuracy-1的数值,而非修改原有的对象结构。第一次调用后,Result状态从包含Accuracy的对象变成了纯数值99,后续再调用时state.Accuracy会变成undefined,计算undefined-1就得到NaN,这就是出现undefined和NaN的核心原因。

  2. useEffect依赖错误导致重复触发
    将handleChange作为useEffect依赖,但handleChange是组件每次渲染都会重新创建的函数,这会导致useEffect在每次渲染时都执行,重复调用handleMatch并触发setAccuracy,造成多次渲染。加上屏蔽了ESLint的依赖检查,进一步掩盖了问题。

  3. 匹配逻辑触发时机不合理
    实际应该监听InputText的变化来执行匹配检查,而非依赖handleChange函数,因为handleMatch的逻辑完全基于输入文本和测试文本的对比。


修复方案

1. 修正Result切片的reducer

保持state的对象结构,正确更新Accuracy值:

reducers:{
    setAccuracy(state,action){
        // Redux Toolkit的Immer允许直接修改state
        state.Accuracy -= 1;
        // 或者返回新对象:return {...state, Accuracy: state.Accuracy - 1}
    }
}

2. 修复useEffect的依赖和触发逻辑

用useCallback包裹handleMatch,并将依赖改为InputText和Test:

import React, { useEffect, useCallback } from 'react';

// ...

const handleMatch = useCallback(() => {
  if(Test === InputText){
    console.log( "a complete match");
      dispatch(setValue(""));
      return;
   } 
   if(Test.includes(InputText)){
    console.log("good going");
    return;
   }
   else{
    console.log(Accuracy);
    dispatch(setAccuracy());
    return;
   }
}, [Test, InputText, Accuracy, dispatch]);

useEffect(()=>{
  handleMatch();
},[handleMatch])

3. 统一InputText的初始状态类型

InputText存储的是字符串输入,将初始状态从数组[]改为空字符串"",避免类型不一致:

const InputTextSlice = createSlice({
    name:"InputText",
    initialState:"",
    reducers:{
        setValue(state,action){
            return action.payload;
        }
    }
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:47:08