首页 > 解决方案 > 我的 useEffect 效果正在运行,但我不确定为什么 - 依赖数组值(似乎)没有改变

问题描述

我正在学习 React 钩子。练习使用useRefanduseEffect钩子的一项任务是构建一个“点击计数”游戏。游戏有一个从 10 开始倒计时的计时器(由useEffect和提供动力setIinterval),一个状态变量计算您在设定的时间内可以点击多少次。

我想超越并继续探索,所以我想添加一个可以“重置”游戏的按钮。我发现我必须添加一个状态值来跟踪游戏是否“活跃”(当倒数计时器达到 0 时游戏不活跃)。为了使重置功能起作用,我必须在依赖数组中列出这个状态值(称为gameIsActive) 。useEffect当倒数计时器归零时,gameIsActive变量从其默认值切换truefalse,单击重置按钮将其切换回true,以及重置其他相关状态值(单击计数归零,计时器归零, 在这种情况下)。

我正在努力理解的是为什么会这样。从 React 文档useEffect看来,添加gameIsActive到依赖数组应该可以防止效果运行,因为在游戏期间, 的值gameIsActive不会改变......文档中的相关措辞:

In the example above, we pass [count] as the second argument. What does this mean? If the count is 5, and then our component re-renders with count still equal to 5, React will compare [5] from the previous render and [5] from the next render. Because all items in the array are the same (5 === 5), React would skip the effect. That’s our optimization.

When we render with count updated to 6, React will compare the items in the [5] array from the previous render to items in the [6] array from the next render. This time, React will re-apply the effect because 5 !== 6. If there are multiple items in the array, React will re-run the effect even if just one of them is different.
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function CounterGame() {
  const [clickCount, setClickCount] = useState(0);
  const [timeRemaining, setTimeRemaining] = useState(10);
  const [gameIsActive, setGameIsActive] = useState(true);
  const id = useRef(null);

  const handleClick = () => {
    setClickCount((clickCount) => clickCount + 1);
  };

  const handleReset = () => {
    setTimeRemaining(10);
    setClickCount(0);
    setGameIsActive(true);
  };

  const clearInterval = () => {
    window.clearInterval(id.current);
  };

  useEffect(() => {
    id.current = window.setInterval(() => {
      setTimeRemaining((timeRemaining) => timeRemaining - 1);
    }, 1000);

    return clearInterval;
    // If gameIsActive is ommitted from the dependency array
    // countdown timer will not restart when game is "reset"
  }, [gameIsActive]);

  useEffect(() => {
    if (timeRemaining === 0) {
      clearInterval();
    }

    setGameIsActive(false);
  }, [timeRemaining]);

  return (
    <div className="App">
      <h3>
        Time remaining (secs):
        {timeRemaining}
      </h3>
      <h3>
        Click Count:
        {clickCount}
      </h3>
      <button onClick={handleClick} disabled={!timeRemaining}>
        Click Me
      </button>
      <button onClick={handleReset}>Reset Game</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<CounterGame />, rootElement);

gameIsActive如果我从第一个钩子的依赖数组中排除 ,useEffect如果计数器为零,则重置将不起作用。我猜测这是因为当计时器达到零时,我清除了间隔,并且从不重新实例化它。添加状态似乎有必要触发效果以设置另一个间隔,因此当我从(当计时器达到零时)回到(当我重置“游戏”时)gameIsActive时这是有意义的。但是为什么每次计时器滴答时效果都会运行?我特别困惑,因为我必须使用钩子来保持从渲染到渲染的间隔 ID,并且该逻辑发生在同一个钩子中。falsetrueuseRef

这是该问题的工作代码和框的链接-您将看到删除gameIsActivefirstuseEffect的依赖数组中的值将导致游戏在计时器达到零后不再工作(尽管如果如果事情会重置并开始倒计时您在计时器达到零之前单击重置)。

标签: reactjsreact-hooksstate

解决方案


它之所以有效,是因为在计时器的第一个滴答声中——它达到了你的 useEffect (时间不是 0),但它将 gameIsActive 设置为 false。单击重置后,它会将其设置回 true - 这会触发 useEffect 重新启动计时器。等等等等。

查看当您注释掉setGameIsActive(false);并尝试重置它时会发生什么——注意它没有重置?


推荐阅读