Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/reactjs/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Reactjs useState()未从事件处理程序更新状态?_Reactjs_React Hooks - Fatal编程技术网

Reactjs useState()未从事件处理程序更新状态?

Reactjs useState()未从事件处理程序更新状态?,reactjs,react-hooks,Reactjs,React Hooks,我正在尝试在React中重新创建一个旧的flash游戏。游戏的目的是按下按钮一段时间 这是一个古老的游戏: 以下是我的新React实现: 我遇到了一个问题。释放鼠标时,我使用Moment.js时间库计算按钮按下和释放之间的时间量。如果onMouseDown和onmousedup事件之间的时差在targetTime范围内,我希望游戏level增加,并且targetTime也增加 我正在handleMouseUp事件处理程序中实现此逻辑。我正在将预期时间打印到屏幕上,但逻辑不起作用。此外,当Ic

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

这是一个古老的游戏:

以下是我的新React实现:

我遇到了一个问题。释放鼠标时,我使用Moment.js时间库计算按钮按下和释放之间的时间量。如果
onMouseDown
onmousedup
事件之间的
时差
targetTime
范围内,我希望游戏
level
增加,并且
targetTime
也增加

我正在
handleMouseUp
事件处理程序中实现此逻辑。我正在将预期时间打印到屏幕上,但逻辑不起作用。此外,当I
console.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;
      });
    }