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

React TypeScript洗牌数组后组件旧状态未重置问题咨询

问题分析与解决方案

问题出在React的组件复用机制上:你渲染Question组件时使用question.id作为key,洗牌后数组顺序改变,但每个Question的id并未变化,React会复用已有的组件实例,不会重新初始化组件,因此组件内部的selectedAnswer状态会保留之前的值——哪怕randomIdx这类props更新,也不会触发状态重置。

以下是三种可行的解决方案:


方案一:修改key强制组件重新挂载

通过给每个Question组件添加动态变化的key,让React认为洗牌后的组件是全新实例,从而重新挂载并重置内部状态。

修改App.tsx:

// 新增洗牌版本号状态
const [shuffleVersion, setShuffleVersion] = useState(0);

const handleChangeSection = (e: React.MouseEvent<HTMLButtonElement>) => {
    const sectionClicked = Number((e.target as HTMLButtonElement).value);

    if (currentSection === sectionClicked) {
        let arr = [...filteredQuestions];
        setFilteredQuestions(shuffleQuestions(arr));
        // 洗牌时递增版本号,触发key变化
        setShuffleVersion(prev => prev + 1);
    } else {
        setCurrentSection(sectionClicked);
    }
};

// 渲染时使用带版本号的key
{ filteredQuestions.map((question, index) => (
    <div ref={refs.current[index]} key={`${question.id}-${shuffleVersion}`}>
        <Question
            question={question}
            setScrollId={setScrollId}
            originalId={question.id}
            randomIdx={index}
        />
    </div>
))}

方案二:在Question组件内监听props重置状态

保持组件复用,通过useEffect监听洗牌后变化的props(比如randomIdx),主动重置内部状态。

修改Question.tsx:

import { useEffect, useState } from "react";
// ...其他代码不变

export default function Question(props: QuestionProps) {
    const { question, setScrollId, originalId, randomIdx } = props;

    const [selectedAnswer, setSelectedAnswer] = useState<AnsweredQuestion>({
        state: null,
    });

    // 监听randomIdx变化,重置选中状态
    useEffect(() => {
        setSelectedAnswer({ state: null });
    }, [randomIdx]);

    // ...其他代码不变
}

方案三:状态提升到App组件统一管理

将每个Question的选中状态移到App组件中统一维护,洗牌时直接清空所有状态,符合React状态管理的最佳实践。

修改App.tsx:

// 新增状态,存储每个题目ID对应的选中状态
const [answeredStates, setAnsweredStates] = useState<Record<number, AnsweredQuestion>>({});

const handleChangeSection = (e: React.MouseEvent<HTMLButtonElement>) => {
    const sectionClicked = Number((e.target as HTMLButtonElement).value);

    if (currentSection === sectionClicked) {
        let arr = [...filteredQuestions];
        setFilteredQuestions(shuffleQuestions(arr));
        // 洗牌时清空所有选中状态
        setAnsweredStates({});
    } else {
        setCurrentSection(sectionClicked);
    }
};

// 传递状态和修改函数给Question组件
{ filteredQuestions.map((question, index) => (
    <div ref={refs.current[index]} key={question.id}>
        <Question
            question={question}
            setScrollId={setScrollId}
            originalId={question.id}
            randomIdx={index}
            selectedAnswer={answeredStates[question.id] || { state: null }}
            setSelectedAnswer={(newState) => {
                setAnsweredStates(prev => ({
                    ...prev,
                    [question.id]: newState
                }));
            }}
        />
    </div>
))}

修改Question.tsx,移除内部状态,改用props传递的状态:

// 扩展Props类型
type QuestionProps = {
    question: QuestionType;
    setScrollId: React.Dispatch<React.SetStateAction<number>>;
    originalId: number;
    randomIdx: number;
    selectedAnswer: AnsweredQuestion;
    setSelectedAnswer: React.Dispatch<React.SetStateAction<AnsweredQuestion>>;
};

export default function Question(props: QuestionProps) {
    // 直接使用props中的状态,移除内部useState
    const { question, setScrollId, originalId, randomIdx, selectedAnswer, setSelectedAnswer } = props;

    const handleAnswerClick = (choice: ChoiceType) => {
        setSelectedAnswer((prev) => {
            const state = choice.isCorrect
                ? AnsweredQuestionState.CORRECT
                : AnsweredQuestionState.WRONG;
            return { ...prev, state };
        });
    };

    // ...其他代码不变
}

方案对比

  • 方案一:实现最简单,适合快速解决问题,但强制重新挂载可能带来轻微性能开销(题目场景下可忽略)。
  • 方案二:保持组件复用,性能更优,仅需修改子组件逻辑。
  • 方案三:推荐的最佳实践,状态统一管理,便于后续扩展(如统计答题结果、批量操作等)。

内容的提问来源于stack exchange,提问作者Kullman Robert Alexandru

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 23:09:52