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

React子组件重渲染疑问:为何QuestionTimer未随父组件更新?

React测验应用计时器未重置问题解析

场景与代码

我正在开发一款React测验应用,包含Quiz和QuestionTimer两个核心组件。当前遇到的问题是:超时跳转至下一题后,进度条和计时器并未重置。通过给QuestionTimer添加key prop强制重建组件可以解决,但我疑惑的是:父组件Quiz因userAnswers状态更新重渲染时,为什么QuestionTimer没有自动重置?

Quiz组件代码

export default function Quiz() {
  const [userAnswers, setUserAnswers] = useState([]);
  const currQuestionIdx = userAnswers.length;
  const quizIsComplete = currQuestionIdx === QUESTIONS.length;

  const handleSelectAnswer = useCallback(
    (answer) => {
      setUserAnswers((prevUserAnswers) => {
        return [...prevUserAnswers, answer];
      });
    },
    []
  ); 

  const handleSkipAnswer = useCallback(() => {
    handleSelectAnswer(null);
  }, [handleSelectAnswer]); 

  if (quizIsComplete) {
    return (
      <div id="summary">
        <img src={quizComplete} alt="Trophy Icon" />
        <h2>Quiz Completed!</h2>
      </div>
    );
  }

  return (
    <div id="quiz">
      <div id="question">
         <QuestionTimer timeout={10000} onTimeout={handleSkipAnswer} />
         {/* 其他展示选项的JSX代码 */}
      </div>
    </div>
  );
}

QuestionTimer组件代码

export default function QuestionTimer({ timeout, handleTimeout }) {
  const [remainingTime, setRemainingTime] = useState(timeout);

  useEffect(() => {
    const timer = setTimeout(handleTimeout, timeout);

    return () => {
      clearTimeout(timer);
    };
  }, [timeout, handleTimeout]);

  useEffect(() => {
    const interval = setInterval(() => {
      setRemainingTime((prevRemainingTime) => {
        return prevRemainingTime - 10;
      });
    }, 10);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return <progress id="question-time" value={remainingTime} max={timeout} />;
}

问题核心原因

父组件Quiz因userAnswers更新重渲染时,QuestionTimer的props没有发生变化,React会复用现有组件实例,导致内部状态和定时器逻辑无法重置,具体拆解如下:

  1. 组件重渲染触发条件不满足:React组件只有在props、自身状态或上下文变化时才会重渲染。这里QuestionTimer的timeout固定为10000,handleSkipAnswer通过useCallback包裹,依赖的handleSelectAnswer也因useCallback空依赖保持稳定,所以props无变化,组件不会触发重渲染。

  2. 内部状态被保留:复用组件实例时,useState初始化的remainingTime会保留上一题结束后的剩余值(比如0),不会重新初始化为timeout值。

  3. 无依赖useEffect不会重新执行:第二个useEffect的依赖数组为空,只会在组件挂载时执行一次。复用组件时不会再次触发,所以进度条的更新定时器不会重启,进度自然无法重置。

加key解决问题的原理

给QuestionTimer添加key(比如key={currQuestionIdx})后,每切换一题key值就会变化。React会判定这是全新的组件实例,销毁旧组件并挂载新组件:新组件的remainingTime会重新初始化为timeout,两个useEffect也会重新执行,计时器和进度条就正常重置了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:05:20