gpt4 book ai didi

reactjs - useState() 没有从事件处理程序更新状态?

转载 作者:行者123 更新时间:2023-12-03 18:34:59 24 4
gpt4 key购买 nike

我正在尝试在 React 中重新创建一个旧的 Flash 游戏。游戏的目的是按下一个按钮一段时间。

这是老游戏:
http://www.zefrank.com/everysecond/index.html

这是我的新 React 实现:
https://codesandbox.io/s/github/inspectordanno/every_second

我遇到了一个问题。释放鼠标时,我使用 Moment.js 时间库计算按钮按下和释放之间的时间量。如果 timeDifference onMouseDown之间和 onMouseUp事件在 targetTime 内, 我要游戏level增加和targetTime也增加。

我在 handleMouseUp 中实现了这个逻辑事件处理程序。我将预期的时间打印到屏幕上,但逻辑不起作用。另外,当我console.log()时代,它们与打印到屏幕上的时代不同。我相当肯定timeHeldtimeDifference没有正确更新。

最初我认为我执行事件处理程序的方式有问题,我需要使用 useRef()useCallback() ,但是在浏览了其他一些问题之后,我对这些问题的理解还不够深入,无法知道在这种情况下是否必须使用它们。由于我不需要访问以前的状态,我认为我不需要使用它们,对吧?

游戏逻辑在这个包装组件中:

import React, { useState } from 'react';
import moment from 'moment';
import Button from './Button';
import Level from './Level';
import TargetTime from './TargetTime';
import TimeIndicator from './TimeIndicator';
import Tries from './Tries';

const TimerApp = () => {
const [level, setLevel] = useState(1);
const [targetTime, setTargetTime] = useState(.2);
const [isPressed, setIsPressed] = useState(false);
const [whenPressed, setPressed] = useState(moment());
const [whenReleased, setReleased] = useState(moment());
const [tries, setTries] = useState(3);
const [gameStarted, setGameStarted] = useState(false);
const [gameOver, setGameOver] = useState(false);

const timeHeld = whenReleased.diff(whenPressed) / 1000;
let timeDifference = Math.abs(targetTime - timeHeld);
timeDifference = Math.round(1000 * timeDifference) / 1000; //rounded

const handleMouseDown = () => {
!gameStarted && setGameStarted(true); //initialize game on the first click
setIsPressed(true);
setPressed(moment());
};

const handleMouseUp = () => {
setIsPressed(false);
setReleased(moment());

console.log(timeHeld);
console.log(timeDifference);

if (timeDifference <= .1) {
setLevel(level + 1);
setTargetTime(targetTime + .2);
} else if (timeDifference > .1 && tries >= 1) {
setTries(tries - 1);
}

if (tries === 1) {
setGameOver(true);
}
};

return (
<div>
<Level level={level}/>
<TargetTime targetTime={targetTime} />
<Button handleMouseDown={handleMouseDown} handleMouseUp={handleMouseUp} isGameOver={gameOver} />
<TimeIndicator timeHeld={timeHeld} timeDifference={timeDifference} isPressed={isPressed} gameStarted={gameStarted} />
<Tries tries={tries} />
{gameOver && <h1>Game Over!</h1>}
</div>
)
}

export default TimerApp;

如果您想检查整个应用程序,请参阅沙盒。

最佳答案

如果您更新函数内部的某些状态,然后尝试在同一个函数中使用该状态,它将不会使用更新的值。函数在调用函数时对状态值进行快照,并在整个函数中使用它。在类组件的 this.setState 中不是这种情况。 ,但在钩子(Hook)中就是这种情况。 this.setState也不会急切地更新值,但它可以根据一些事情在同一个函数中更新(我没有足够的资格来解释)。
要使用更新的值,您需要一个引用。因此使用 useRef钩。 [ docs ]
我已经修复了你的代码,你可以在这里看到它:https://codesandbox.io/s/everysecond-4uqvv?fontsize=14
它可以用更好的方式编写,但你必须自己做。

在答案中添加代码以完成(带有一些注释来解释内容,并提出改进建议):

import React, { useRef, useState } from "react";
import moment from "moment";
import Button from "./Button";
import Level from "./Level";
import TargetTime from "./TargetTime";
import TimeIndicator from "./TimeIndicator";
import Tries from "./Tries";

const TimerApp = () => {
const [level, setLevel] = useState(1);
const [targetTime, setTargetTime] = useState(0.2);
const [isPressed, setIsPressed] = useState(false);
const whenPressed = useRef(moment());
const whenReleased = useRef(moment());
const [tries, setTries] = useState(3);
const [gameStarted, setGameStarted] = useState(false);
const [gameOver, setGameOver] = useState(false);
const timeHeld = useRef(null); // make it a ref instead of just a variable
const timeDifference = useRef(null); // make it a ref instead of just a variable

const handleMouseDown = () => {
!gameStarted && setGameStarted(true); //initialize game on the first click
setIsPressed(true);
whenPressed.current = moment();
};

const handleMouseUp = () => {
setIsPressed(false);
whenReleased.current = moment();

timeHeld.current = whenReleased.current.diff(whenPressed.current) / 1000;
timeDifference.current = Math.abs(targetTime - timeHeld.current);
timeDifference.current = Math.round(1000 * timeDifference.current) / 1000; //rounded

console.log(timeHeld.current);
console.log(timeDifference.current);

if (timeDifference.current <= 0.1) {
setLevel(level + 1);
setTargetTime(targetTime + 0.2);
} else if (timeDifference.current > 0.1 && tries >= 1) {
setTries(tries - 1);
// consider using ref for tries as well to get rid of this weird tries === 1 and use tries.current === 0
if (tries === 1) {
setGameOver(true);
}
}
};

return (
<div>
<Level level={level} />
<TargetTime targetTime={targetTime} />
<Button
handleMouseDown={handleMouseDown}
handleMouseUp={handleMouseUp}
isGameOver={gameOver}
/>
<TimeIndicator
timeHeld={timeHeld.current}
timeDifference={timeDifference.current}
isPressed={isPressed}
gameStarted={gameStarted}
/>
<Tries tries={tries} />
{gameOver && <h1>Game Over!</h1>}
</div>
);
};

export default TimerApp;

PS:不要使用不必要的第三方库,尤其是像 MomentJs 这样的大库。它们显着增加了您的捆绑包大小。使用 vanilla js 可以轻松获取当前时间戳。 Date.now()将为您提供当前的 unix 时间戳,您可以减去两个时间戳以获得以毫秒为单位的持续时间。

此外,您还有一些不必要的状态,例如 gameOver ,您可以查看 if tries > 0决定gameOver。
类似地而不是 targetTime你可以使用 level * .2 ,不需要额外的状态。
还有 whenReleased不需要是引用或状态,它可以只是 mouseup 处理程序中的局部变量。

关于reactjs - useState() 没有从事件处理程序更新状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56614051/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com