Reactjs useState()未从事件处理程序更新状态?
我正在尝试在React中重新创建一个旧的flash游戏。游戏的目的是按下按钮一段时间 这是一个古老的游戏: 以下是我的新React实现: 我遇到了一个问题。释放鼠标时,我使用Moment.js时间库计算按钮按下和释放之间的时间量。如果Reactjs useState()未从事件处理程序更新状态?,reactjs,react-hooks,Reactjs,React Hooks,我正在尝试在React中重新创建一个旧的flash游戏。游戏的目的是按下按钮一段时间 这是一个古老的游戏: 以下是我的新React实现: 我遇到了一个问题。释放鼠标时,我使用Moment.js时间库计算按钮按下和释放之间的时间量。如果onMouseDown和onmousedup事件之间的时差在targetTime范围内,我希望游戏level增加,并且targetTime也增加 我正在handleMouseUp事件处理程序中实现此逻辑。我正在将预期时间打印到屏幕上,但逻辑不起作用。此外,当Ic
onMouseDown
和onmousedup
事件之间的时差
在targetTime
范围内,我希望游戏level
增加,并且targetTime
也增加
我正在handleMouseUp
事件处理程序中实现此逻辑。我正在将预期时间打印到屏幕上,但逻辑不起作用。此外,当Iconsole.log()
time时,它们与打印到屏幕上的时间不同。我相当肯定timehold
和timeDifference
没有正确更新
起初,我认为我处理事件处理程序的方式有问题,我需要使用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;
import React,{useState}来自“React”;
从“力矩”中导入力矩;
从“./按钮”导入按钮;
从“./Level”导入级别;
从“/TargetTime”导入TargetTime;
从“./TimeIndicator”导入时间指示器;
从“/尝试”导入尝试;
常数TimerApp=()=>{
const[level,setLevel]=useState(1);
常量[targetTime,setTargetTime]=useState(.2);
const[isPressed,setIsPressed]=useState(false);
const[whenPressed,setPressed]=使用状态(矩());
const[whenReleased,setReleased]=useState(矩());
const[trys,settrys]=useState(3);
const[gameStarted,setGameStarted]=useState(false);
const[gameOver,setGameOver]=使用状态(false);
常数时间保持=释放时差异(按下时)/1000;
让时间差=Math.abs(targetTime-timehold);
时差=数学圆整(1000*时差)/1000;//四舍五入
常量handleMouseDown=()=>{
!gameStarted&&setGameStarted(true);//第一次单击就初始化游戏
setIsPressed(真);
设置按下(力矩());
};
const handleMouseUp=()=>{
设置压力(假);
设定释放(力矩());
console.log(timehold);
console.log(时差);
if(时差.1&&trys>=1){
设置尝试次数(尝试次数-1);
}
如果(尝试===1){
setGameOver(真);
}
};
返回(
{gameOver&&Game Over!}
)
}
导出默认TimerApp;
如果你想检查整个应用程序,请参考沙盒
当鼠标被释放时,我计算按下按钮和释放按钮之间的时间量
这不是真的。。。但是可以。。。只需将时差计算移动到handleMouseUp()
。。。另外-在发布时,您不需要
,
我认为这里发生了两件微妙的事情:
setState
方法(例如setRelease(moment())
)时,关联变量的值(例如whenReleased
)不会立即更新。相反,它会对重新渲染进行排队,并且只有在该渲染发生后,才会更新该值handleMouseUp
)是闭包。这意味着它们从父范围捕获值。同样,仅在重新渲染时更新。因此,当运行handleMouseUp
时,timeDifference
(和timehold
)将是上次渲染期间计算的值时差的计算移动到handleMouseUp
事件处理程序中
moment()
(您也可以通过setReleased
设置whenReleased
,但该值在事件处理程序中不可用)的局部变量,而不是在timeDifference
计算中使用whenReleased
consthandlemouseup=()=>{
释放常数=力矩();
设置压力(假);
释放(释放);
常数时间保持=释放。差异(按下时)/1000;
const timeDifference=Math.round(1000*Math.abs(targetTime-timehold))/1000;
console.log(timehold);
console.log(时差);
if(时差.1&&trys>=1){
设置尝试次数(尝试次数-1);
}
如果(尝试===1){
setGameOver(真);
}
};
如果您更新函数中的某些状态,然后尝试在同一函数中使用该状态,它将不会使用更新后的值。函数在调用函数时快照状态值,并在整个函数中使用该值。在类组件的This.setState
中不是这种情况,但在钩子中就是这种情况this.setState
也不会急于更新值,但它可以在同一个函数中更新,这取决于一些事情(我没有资格解释)。要使用更新的值,您需要一个ref。因此,请使用
useRef
hook。[]我已经修复了您的代码,您可以在此处看到:
它可以用更好的方式写,但你必须自己写 在answer中添加代码以完成(带有一些注释以解释内容,并提出改进建议):
import React,{useRef,useState}来自“React”;
从“时刻”中导入时刻;
从“/”按钮导入按钮;
从“/Level”导入级别;
进口T
const handleMouseUp = () => {
const released = moment();
setIsPressed(false);
setReleased(released);
const timeHeld = released.diff(whenPressed) / 1000;
const timeDifference = Math.round(1000 * Math.abs(targetTime - timeHeld)) / 1000;
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);
}
};
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;
[value, setValue ] = useState(initial);
...
setValue(value + change);
[value, setValue ] = useState(initial);
...
setValue((curValue) => curValue + change);
if (timeDifference <= .1) {
setLevel((curLevel) => curLevel + 1);
setTargetTime((curTarget) => curTarget + .2);
} else if (timeDifference > .1 && tries >= 1) {
setTries((curTries) => {
const newTries = curTries - 1;
if (newTries === 1) {
setGameOver(true);
}
return newTries;
});
}