reactjs - 我的 useEffect 效果正在运行,但我不确定为什么 - 依赖数组值(似乎)没有改变
问题描述
我正在学习 React 钩子。练习使用useRef
anduseEffect
钩子的一项任务是构建一个“点击计数”游戏。游戏有一个从 10 开始倒计时的计时器(由useEffect
和提供动力setIinterval
),一个状态变量计算您在设定的时间内可以点击多少次。
我想超越并继续探索,所以我想添加一个可以“重置”游戏的按钮。我发现我必须添加一个状态值来跟踪游戏是否“活跃”(当倒数计时器达到 0 时游戏不活跃)。为了使重置功能起作用,我必须在依赖数组中列出这个状态值(称为gameIsActive
) 。useEffect
当倒数计时器归零时,gameIsActive
变量从其默认值切换true
到false
,单击重置按钮将其切换回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,并且该逻辑发生在同一个钩子中。false
true
useRef
这是该问题的工作代码和框的链接-您将看到删除gameIsActive
firstuseEffect
的依赖数组中的值将导致游戏在计时器达到零后不再工作(尽管如果如果事情会重置并开始倒计时您在计时器达到零之前单击重置)。
解决方案
它之所以有效,是因为在计时器的第一个滴答声中——它达到了你的 useEffect (时间不是 0),但它将 gameIsActive 设置为 false。单击重置后,它会将其设置回 true - 这会触发 useEffect 重新启动计时器。等等等等。
查看当您注释掉setGameIsActive(false);
并尝试重置它时会发生什么——注意它没有重置?
推荐阅读
- frontend - 仅当存在时才在 .tpl smarty 文件中打印变量的正确方法
- dialogflow-cx - Dialogflow CX - 如何让 sys.no-input-1 重复而不是转到 sys.no-input-default
- php - PHP在创建对象时从数组中获取对象
- reactjs - 如何加载组件中的所有 url,并且 onclick 应该使用特定于所选项目的状态变量填充表?
- java - XSD 双重绑定到 BigDecimal
- signature - 椭圆曲线数字签名格式
- c# - 带有标签页的 Xamarin 表单在每个标签页中都有数据表单输入页面
- git - 如何更改 Azure DevOps 主机 Git Repo 的默认分支名称
- swift - SwiftUI 在按钮内获取 SF 图像
- python - ZeroDivisionError 即使在使用 try 和 except 之后