Reactjs setInterval和React挂钩会产生意外的结果

Reactjs setInterval和React挂钩会产生意外的结果,reactjs,react-hooks,Reactjs,React Hooks,我在我的应用程序中使用create react定义了以下组件: import React, { useState } from 'react'; const Play = props => { const [currentSecond, setCurrentSecond] = useState(1); let timer; const setTimer = () => { timer = setInterval(() => { if (curr

我在我的应用程序中使用create react定义了以下组件:

import React, { useState } from 'react';

const Play = props => {
  const [currentSecond, setCurrentSecond] = useState(1);
  let timer;
  const setTimer = () => {
    timer = setInterval(() => {
      if (currentSecond < props.secondsPerRep) {
        setCurrentSecond(() => currentSecond + 1);
      }
    }, 1000);
  }
  setTimer();
  return (
    <div>
      <div>
        <p>{currentSecond}</p>
      </div>

    </div>
  );
}

export default Play;
然后
setInterval
回调中的
currentSecond
始终返回初始值,即1


非常感谢您的帮助

您的问题是这一行
setCurrentSecond(()=>currentSecond+1)
因为您只调用一次
setTimer
,所以在
currentSecond
为1的初始状态下,您的间隔将始终关闭

幸运的是,您可以通过传递给
setCurrentSecond
的函数中的参数轻松地访问实际的当前状态,如
setCurrentSecond(actualCurrentSecond=>actualCurrentSecond+1)

另外,您需要非常小心地任意定义功能组件主体中的间隔,因为它们不会被正确清除,例如,如果再次单击按钮,它将启动另一个间隔,而不会清除上一个间隔


我建议你看看这篇博文,因为它会回答你关于Interval+hooks的任何问题:

你的问题是这行
setCurrentSecond(()=>currentSecond+1)
因为您只调用一次
setTimer
,所以在
currentSecond
为1的初始状态下,您的间隔将始终关闭

幸运的是,您可以通过传递给
setCurrentSecond
的函数中的参数轻松地访问实际的当前状态,如
setCurrentSecond(actualCurrentSecond=>actualCurrentSecond+1)

另外,您需要非常小心地任意定义功能组件主体中的间隔,因为它们不会被正确清除,例如,如果再次单击按钮,它将启动另一个间隔,而不会清除上一个间隔


我建议你看看这篇博文,因为它会回答你关于Interval+hooks的任何问题:

这是因为你的代码在你点击按钮之前正在关闭来自渲染的
currentSecond
值。也就是说javascript不知道重新呈现和挂钩。您确实希望以稍微不同的方式进行设置

import React, { useState, useRef, useEffect } from 'react';

const Play = ({ secondsPerRep }) => {
  const secondsPassed = useRef(1)
  const [currentSecond, setCurrentSecond] = useState(1);
  const [timerStarted, setTimerStarted] = useState(false)

  useEffect(() => {
    let timer;

    if(timerStarted) {
      timer = setInterval(() => {
        if (secondsPassed.current < secondsPerRep) {
          secondsPassed.current =+ 1

          setCurrentSecond(secondsPassed.current)
        }
      }, 1000);
    }

    return () => void clearInterval(timer)
  }, [timerStarted])

  return (
    <div>
      <div>
        <button onClick={() => setTimerStarted(!timerStarted)}>
          {timerStarted ? Stop : Start}
        </button>
        <p>{currentSecond}</p>
      </div>

    </div>
  );
}

export default Play;
import React,{useState,useRef,useffect}来自'React';
常量播放=({secondsPerRep})=>{
const secondsPassed=useRef(1)
常数[currentSecond,setCurrentSecond]=useState(1);
常量[timerStarted,setTimerStarted]=useState(false)
useffect(()=>{
让定时器;
如果(计时器开始){
计时器=设置间隔(()=>{
if(第二个经过的电流<第二个经过的电流){
secondsPassed.current=+1
setCurrentSecond(secondsPassed.current)
}
}, 1000);
}
return()=>void clearInterval(计时器)
},[timerStarted])
返回(
setTimerStarted(!timerStarted)}>
{timerStarted?停止:开始}
{currentSecond}

); } 导出默认播放;
为什么需要
ref
和状态?如果您只拥有状态,则每次更新状态时,效果的清理方法都会运行。因此,您不希望您的状态影响您的效果。您可以通过使用
ref
计数秒数来实现这一点。对ref的更改不会运行该效果或清除它

但是,您还需要该状态,因为您希望组件在满足条件后重新渲染。但由于状态的更新程序方法(即,
setCurrentSecond
)是常量,因此它们也不会影响效果

最后但并非最不重要的一点是,我已将设置间隔与计数逻辑分离。我使用了一个额外的状态在
true
false
之间切换。因此,当您单击按钮时,状态将切换到
true
,效果将运行,一切都已设置好。如果要卸载组件,或停止计时器,或
secondsPerRep
道具更改,则清除旧间隔并设置新间隔


希望有帮助

这是因为在单击按钮之前,代码正在关闭渲染中的
currentSecond
值。也就是说javascript不知道重新呈现和挂钩。您确实希望以稍微不同的方式进行设置

import React, { useState, useRef, useEffect } from 'react';

const Play = ({ secondsPerRep }) => {
  const secondsPassed = useRef(1)
  const [currentSecond, setCurrentSecond] = useState(1);
  const [timerStarted, setTimerStarted] = useState(false)

  useEffect(() => {
    let timer;

    if(timerStarted) {
      timer = setInterval(() => {
        if (secondsPassed.current < secondsPerRep) {
          secondsPassed.current =+ 1

          setCurrentSecond(secondsPassed.current)
        }
      }, 1000);
    }

    return () => void clearInterval(timer)
  }, [timerStarted])

  return (
    <div>
      <div>
        <button onClick={() => setTimerStarted(!timerStarted)}>
          {timerStarted ? Stop : Start}
        </button>
        <p>{currentSecond}</p>
      </div>

    </div>
  );
}

export default Play;
import React,{useState,useRef,useffect}来自'React';
常量播放=({secondsPerRep})=>{
const secondsPassed=useRef(1)
常数[currentSecond,setCurrentSecond]=useState(1);
常量[timerStarted,setTimerStarted]=useState(false)
useffect(()=>{
让定时器;
如果(计时器开始){
计时器=设置间隔(()=>{
if(第二个经过的电流<第二个经过的电流){
secondsPassed.current=+1
setCurrentSecond(secondsPassed.current)
}
}, 1000);
}
return()=>void clearInterval(计时器)
},[timerStarted])
返回(
setTimerStarted(!timerStarted)}>
{timerStarted?停止:开始}
{currentSecond}

); } 导出默认播放;
为什么需要
ref
和状态?如果您只拥有状态,则每次更新状态时,效果的清理方法都会运行。因此,您不希望您的状态影响您的效果。您可以通过使用
ref
计数秒数来实现这一点。对ref的更改不会运行该效果或清除它

但是,您还需要该状态,因为您希望组件在满足条件后重新渲染。但由于状态的更新程序方法(即,
setCurrentSecond
)是常量,因此它们也不会影响效果

最后但并非最不重要的一点是,我已将设置间隔与计数逻辑分离。我使用了一个额外的状态在
true
false
之间切换。所以当你点击你的
const Play = props => {
  const [currentSecond, setCurrentSecond] = useState(1);
  const [timer, setTimer] = useState();
  const onClick = () => {
    setTimer(setInterval(() => {
      setCurrentSecond((state) => {
        if (state < props.secondsPerRep) {
          return state + 1;
        }
        return state;
      });
    }, 1000));
  }

  return (
    <div>
      <div>
        <button onClick={onClick} disabled={timer}>Start</button>
        <p>{currentSecond}</p>
      </div>

    </div>
  );
}
const Play = props => {
  const [currentSecond, setCurrentSecond] = React.useState(1);
  const [isRunning, setIsRunning] = React.useState(false);
  useInterval(() => {
    if (currentSecond < props.secondsPerRep) {
      setCurrentSecond(currentSecond + 1);
    }
  }, isRunning ? 1000 : null);

  return (
    <div>
      <div>
        <button onClick={() => setIsRunning(true)}>Start</button>
        <p>{currentSecond}</p>
      </div>
    </div>
  );
}