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

ReactJS useReducer触发两个不同Action问题排查与解决

排序可视化工具的Action调度问题及修复方案

我正在构建一个排序算法可视化工具,点击按钮后每秒对整数数组中的一个元素进行排序。但点击“selection sort”按钮时,会先调度“randomize”类型的action,再调度“selection sort”的action,且该按钮实际仅起到“randomize”按钮的作用,无法完成排序。请问我是否错误使用了useReducer Hook?如何让它仅调度一个action?


arr-context-provider.tsx

import { createContext, useReducer } from "react";
import ControlPanel from './components/control-panel';
import Visualizer from './components/visualizer';

interface ArrayContextProps {
  children: React.ReactElement;
}

export type Action = 
  | { type: "selection sort" }
  | { type: "randomize" }

const initArr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
export const arrContext = createContext<[number[], React.Dispatch<Action>]>([initArr, () => initArr]);

const ArrContextProvider: React.FC<ArrayContextProps> = ({ children }: ArrayContextProps) => {
  const arrReducer = (arr: number[], action: Action) => {
    switch (action.type) {
        case "selection sort":
          for (let i = 0; i < arr.length; i++) {
            setTimeout(() => {
              let minIndex = i;
              for (let j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[minIndex]) {
                  minIndex = j;
                }
              }
              if (minIndex !== i) {
                [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
              }
              
              console.log("selection sort!");
              return [...arr.slice()];
            }, 100 * (i + 1));
          }
        
        case "randomize":
          arr = [];
          while (arr.length < 10) {
            let rng = Math.floor(Math.random() * 10) + 1;
            if (arr.indexOf(rng) === -1) {
                arr.push(rng);
            }
          }

          console.log("randomize!");
          return [...arr.slice()];
        
        default:
          console.log("error!");
          return [...arr.slice()];
    }
  }

  const [arr, dispatch] = useReducer<(arr: number[], action: Action) => number[]>(arrReducer, initArr);
  return (
    <arrContext.Provider value={[arr, dispatch]}>
      <div>
        <ControlPanel />
        <Visualizer />
      </div>
    </arrContext.Provider>
  );
};

export default ArrContextProvider;

control-panel.tsx

import { useState, useEffect, useContext } from 'react';
import { arrContext, Action } from './arr-context-provider';

const ControlPanel: React.FC = () => {
  const [arr, dispatch] = useContext<[number[], React.Dispatch<Action>]>(arrContext);
  const [algorithm, setAlgorithm] = useState("");

  useEffect(() => {
    switch(algorithm) {
      case "selection sort":
        dispatch({ type: "selection sort" });
      case "randomize":
        dispatch({ type: "randomize" });
        setAlgorithm("");
      default:
        [...arr.slice()];
    }
  }, [algorithm]);

  return (
    <div className='headerContainer'>
      <h1 className='header'>🆂🅾🆁🆃🅸🅽🅶 🅰🅻🅶🅾🆁🅸🆃🅷🅼 🆅🅸🆂🆄🅰🅻🅸🆉🅴🆁</h1>
      <li className='list'>
        <ul><button className='button' onClick={() => setAlgorithm("selection sort")}>Selection Sort</button></ul>
        <ul><button className='button' onClick={() => setAlgorithm("randomize")}>Randomize</button></ul>
      </li>
    </div>
  )
}
  
export default ControlPanel;

问题分析与修复

1. 核心问题:Switch语句缺少Break

你的ControlPanel组件中,useEffect里的switch语句未添加break,导致当algorithm为"selection sort"时,执行完选择排序的dispatch后,会继续执行下一个case的随机化代码,同时调度两个action。这就是点击选择排序按钮却触发随机化的直接原因。

修复代码:

useEffect(() => {
  switch(algorithm) {
    case "selection sort":
      dispatch({ type: "selection sort" });
      setAlgorithm("");
      break;
    case "randomize":
      dispatch({ type: "randomize" });
      setAlgorithm("");
      break;
    default:
      break;
  }
}, [algorithm]);

2. Reducer异步逻辑错误

React的useReducer要求reducer是纯函数,不能包含异步操作(如setTimeout)。你当前在reducer中用定时器执行排序逻辑,会导致:

  • Reducer会立即执行完循环,不会等待定时器回调
  • 回调中的return无法被reducer捕获,状态根本不会更新

正确方案:将异步调度移至组件,分步骤触发排序

步骤1:扩展Action类型

添加支持单步排序的action,传递当前处理索引:

export type Action = 
  | { type: "selection_sort_step", index: number }
  | { type: "randomize" }

步骤2:修改Reducer处理单步排序

让reducer仅处理当前步骤的排序,返回新数组:

const arrReducer = (arr: number[], action: Action) => {
  switch (action.type) {
    case "selection_sort_step": {
      const i = action.index;
      const newArr = [...arr];
      let minIndex = i;
      for (let j = i + 1; j < newArr.length; j++) {
        if (newArr[j] < newArr[minIndex]) {
          minIndex = j;
        }
      }
      if (minIndex !== i) {
        [newArr[i], newArr[minIndex]] = [newArr[minIndex], newArr[i]];
      }
      return newArr;
    }
    case "randomize": {
      const newArr: number[] = [];
      while (newArr.length < 10) {
        let rng = Math.floor(Math.random() * 10) + 1;
        if (newArr.indexOf(rng) === -1) {
          newArr.push(rng);
        }
      }
      return newArr;
    }
    default:
      return arr;
  }
}

步骤3:在组件中调度单步排序

修改ControlPanel的useEffect,通过定时器依次触发每一步:

useEffect(() => {
  switch(algorithm) {
    case "selection sort":
      for (let i = 0; i < arr.length; i++) {
        setTimeout(() => {
          dispatch({ type: "selection_sort_step", index: i });
        }, 1000 * (i + 1)); // 每秒执行一步
      }
      setAlgorithm("");
      break;
    case "randomize":
      dispatch({ type: "randomize" });
      setAlgorithm("");
      break;
    default:
      break;
  }
}, [algorithm, arr.length, dispatch]);

3. 其他注意事项

  • 永远不要直接修改reducer中的原数组,必须返回新数组(如const newArr = [...arr]),否则React无法检测状态变化
  • 原代码中default case的[...arr.slice()]无意义,直接返回原数组即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 11:45:01