React状态更新:useState与useReducer的区别

60 阅读4分钟

1. 问题与最终解决办法

问题: 模拟模型任务运行进程的状态变化,包括"Check data format", "Data preprocessing", "Model core computing", "Output result generation in progress","Model execution finished!"五个状态,useState能够成功设置"Check data format"状态,但是ModelExecuteProcess中接收到的初始状态是"Model core computing",永远都比steps[i]状态晚一步,尝试了很多方法未果,最终替换了useReducer后成功实现状态同步更新。

错误结果:

image-20251204223503827.png

steps[0]: Check data format

status: ['Check data format']
steps[1]: Data preprocessing
status: ['Check data format', 'Data preprocessing']
steps[2]: Model core computing
status: ['Check data format', 'Data preprocessing', 'Model core computing']
steps[3]: Output result generation in progress
status: ['Check data format', 'Data preprocessing', 'Model core computing', 'Output result generation in progress']
status: ['Check data format', 'Data preprocessing', 'Model core computing', 'Output result generation in progress', 'Model execution finished!']

成功结果:

image-20251204222959366.png

steps[0]: Check data format
status: ['Data preprocessing']
steps[1]: Data preprocessing
status: [ 'Data preprocessing', 'Model core computing']
steps[2]: Model core computing
status: ['Data preprocessing', 'Model core computing', 'Output result generation in progress']
steps[3]: Output result generation in progress
status: ['Check data format', 'Data preprocessing', 'Model core computing', 'Output result generation in progress']
status: ['Check data format', 'Data preprocessing', 'Model core computing', 'Output result generation in progress', 'Model execution finished!']

部分代码:

  • Decision.tsx
{/*  useState  */}
// const handleRun = () => {
  //   setRunStatus([]);
  //   setIsRunning(true);
  //   // Simulate model execution process update...
  //   const steps = ["Check data format", "Data preprocessing", "Model core computing", "Output result generation in progress"];
  //   let i = 0;
    
  //   const executeStep = () => {
  //     if (i < steps.length) {
  //       console.log("i:", i);
  //       console.log("steps[i]:", steps[i]);
  //       setRunStatus(prev => {
  //         if (steps[i]) {
  //           return [...prev, steps[i]]
  //         }
  //         return prev;
  //       });

  //       i++;
  //       setTimeout(executeStep, 1000);
  //     } else {
  //       console.log("Process finished logic triggered.");
  //       setRunStatus(prev => {
  //         if (prev[prev.length - 1] !== "Model execution finished!") {
  //           return [...prev, "Model execution finished!"]
  //         } return prev;
  //       });
  //     }
  //   }
  //   setTimeout(executeStep, 100);
  // }

{/*  useReducer  */}
type Action = { type: 'ADD_STEP', payload: string } | { type: 'RESET' };
function runStatusReducer(state: String[], action: Action): String[] {
  switch (action.type) {
    case 'ADD_STEP':
      return [...state, action.payload];
    case 'RESET':
      return [];
    default:
      return state;
  }
}

const handleRun = () => {
    setIsRunning(true);
    dispatch({ type: 'RESET' });

    const steps = ["Check data format", "Data preprocessing", "Model core computing", "Output result generation in progress"];
    let i = 0;

    const executeStep = () => {
      if (i < steps.length) {
        console.log("i:", i);
        console.log("steps[i]:", steps[i]);

        dispatch({ type: 'ADD_STEP', payload: steps[i] });

        i++;
        setTimeout(executeStep, 1000);
      } else {
        dispatch({ type: 'ADD_STEP', payload: "Model execution finished!" });
      }
    }
    executeStep();
  }
  • ModelExecuteProcess.tsx
import React from "react";
import { CheckCircle, Loader } from "lucide-react";
import { motion } from "framer-motion";

interface ModelExecuteProcessProps {
    status: String[];
}

const ModelExecuteProcess: React.FC<ModelExecuteProcessProps> = ({ status }) => {
    console.log("status: ", status);
    // get the last process status => current running progress status
    const lastStatus = status[status.length - 1];

    // judge whether the current progress is finished 
    const isProcessFinished = status.length > 0 && lastStatus?.endsWith("finished!");

    const currentStepIndex = isProcessFinished ? status.length : Math.max(0, status.length - 1);
    const processSteps = ["Check data format", "Data preprocessing", "Model core computing", "Output result generation in progress", "Model execution finished!"];

    return (
    ......
    );
};

export default ModelExecuteProcess;

2. useState:简单直接的状态替换

  • 结构

const [state, setState] = useState(initialState);
  • 工作原理

setState 函数可以直接接收新的状态值,或者一个接收上一个状态并返回新状态的函数。无论采用哪种方式,setState 的核心作用是替换整个状态值。

  • 特征

代码量少,对简单状态非常直观;对于只需要获取和设置单个值的非常方便便捷。但是当状态更新逻辑复杂时,会散布在各个处理函数当中,难以维护,此外会涉及批量处理、竞态情况,这也是我一直无法解决的问题。

3. useReducer:可预测的状态替换

  • 结构

const [state, dispatch] = useReducer(reducer, initialState, init);
  • 工作原理

reducer 函数:它接收当前的 state 和一个 action 对象,然后根据 action 的类型返回一个新的状态。它将状态的计算逻辑与状态的使用分离开来。

const reducer = (state: String[], action: Action): String[] {
  switch (action.type) {
    case 'ADD_STEP':
      return [...state, action.payload];
    case 'RESET':
      return [];
    default:
      return state;
  }
};

dispatch 函数: 用于触发状态更新。调用dispatch({ type: 'ADD_STEP', payload: steps[i] }),React 就会将这个 action 传递给 reducer 函数进行状态切换,进行后续的计算操作。

  • 特征

所有的状态转换逻辑都封装在reducer函数中,组件内部只需要调用dispatch,代码更具有可读性;并且适合处理包含多个值的复杂状态对象;由于reducer是纯函数,增强了代码的可预测性,当遇到异步、按顺序追加的场景时,dispatch比setState更可靠。

useStateuseReducer 都是React提供的Hook,用于在函数组件中管理状态。它们都能实现更新状态并触发组件重新渲染,但在使用场景、复杂度和可预测性方面有显著区别。

4. 何时选择哪个Hook?

场景推荐 Hook原因
简单状态useState状态是独立的原始值或简单对象,更新逻辑只有一两种
状态关联useReducer新状态依赖于前一个或多个旧状态,或者状态更新逻辑分散在多个函数中
异步序列useReducer需要严格控制异步操作(如setTimeout或API调用)后的状态更新顺序和内容
大型应用useReducer状态逻辑需要被共享、复用,或者需要使用Context API来进行全局管理。

上述是小李初次接触React的一点点理解,希望能慢慢积累进步,若存在错误请指出,谢谢大家!!!