【问题标题】:State doesn't update inside setInterval状态不会在 setInterval 内更新
【发布时间】:2019-09-17 01:07:05
【问题描述】:

我正在制作一个简单的进度条,当我使用 setInterval 时,我注意到挂钩状态的奇怪行为。这是我的示例代码:

const {useState} = React;

const Example = ({title}) => {
  const [count, setCount] = useState(0);
  const [countInterval, setCountInterval] = useState(0)
  
  let intervalID
 
  const handleCount = () => {
    setCount(count + 1)
    console.log(count)
  }  
    
  const progress = () => {
    intervalID = setInterval(() => {
		setCountInterval(countInterval => countInterval + 1)
		console.log(countInterval)
		if(countInterval > 100) { // this is never reached
      setCountInterval(0)
      clearInterval(intervalID)
		}
	},100)
  }
  
  const stopInterval = () => {
    clearInterval(intervalID)
  }
    
  return (
    <div>
      <p>{title}</p>
      <p>You clicked {count} times</p>
      <p>setInterval count { countInterval } times</p>
      <button onClick={handleCount}>
        Click me
      </button>
      <button onClick={progress}>
        Run interval
      </button>
      <button onClick={stopInterval}>
        Stop interval
      </button>
    </div>
  );
};

// Render it
ReactDOM.render(
  <Example title="Example using simple hook:" />,
  document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>

如果我通过handleCount 设置状态,一切都会按预期发生,但是当我运行progress 函数时,内部 setInterval countInterval不会改变强>在所有。不管怎样,countInterval在状态上发生了变化。

为了解决这个问题,我在 progress 函数中使用了变量,如下所示:

  const progress = () => {
    let internalValue = 0
    intervalID = setInterval(() => {
        setCountInterval(internalValue)
        internalValue++
        if(internalValue > 100) {
      setCountInterval(0)
      clearInterval(intervalID)
        }
    },100)
  }

这很好用,但我仍然想知道是否有更好的方法以及在第一种情况下我做错了什么。

第二个问题是我无法清除progress 函数之外的间隔,我不确定我在这里做错了什么或者我错过了什么?提前感谢您的任何帮助和建议。

【问题讨论】:

  • 我认为你应该使用effect hook 来处理类似的事情。
  • 为什么不只是setCountInterval(countInterval =&gt; countInterval &gt; 100 ? 0 : countInterval + 1)
  • @JonasWilms 这很聪明,但我还需要以某种方式清除Interval。

标签: javascript reactjs setinterval react-hooks


【解决方案1】:

您的问题是由于您的计时器引用在重新渲染时丢失,以及setInterval 回调引用了setCountInterval 的过期版本。

为了让它充分发挥作用,我建议添加一个状态变量来跟踪它是否已启动,并添加一个useEffect 来处理setInterval 的设置和清除。

const Example = ({ title }) => {
  const [count, setCount] = useState(0);
  const [countInterval, setCountInterval] = useState(0);
  const [started, setStarted] = useState(false);

  const handleCount = () => {
    setCount(count + 1);
    console.log(count);
  };

  useEffect(() => {
    let intervalID;
    if (started) {
      intervalID = setInterval(() => {
        setCountInterval(countInterval => countInterval + 1);
        console.log(countInterval);
        if (countInterval > 100) {
          setCountInterval(0);
          setStarted(false);
        }
      }, 100);
    } else {
      clearInterval(intervalID);
    }
    return () => clearInterval(intervalID);
  }, [started, countInterval]);

  return (
    <div>
      <p>{title}</p>
      <p>You clicked {count} times</p>
      <p>setInterval count {countInterval} times</p>
      <button onClick={handleCount}>Click me</button>
      <button onClick={() => setStarted(true)}>Run interval</button>
      <button onClick={() => setStarted(false)}>Stop interval</button>
    </div>
  );
};

这里的工作沙盒:https://codesandbox.io/s/elegant-leaf-h03mi

【讨论】:

  • 谢谢。这是我所期望的。
【解决方案2】:

countInterval 是一个局部变量,它包含一个原始值。由于 JavaScript 的本质,setState 无法以任何方式改变该变量。相反,它重新执行整个Example 函数,然后useState 将返回新的更新值。这就是为什么在组件重新呈现之前您无法访问更新的状态。

您的问题可以通过将条件移动到状态更新回调中轻松解决:

 setCountInterval(countInterval => countInterval > 100 ? 0 : countInterval + 1)

由于重新渲染,您不能使用局部变量,因此每次重新渲染时都会重新创建intervalId(并且它的值会丢失)。使用 useRef 在重新渲染中使用值。

  const interval = useRef(undefined);
  const [count, setCount] = useState(0);

  function stop() { 
    if(!interval.current) return;
    clearInterval(interval.current);
    interval.current = null;
  }

  function start() {
    if(!interval.current) interval.current = setInterval(() => {
      setCount(count => count > 100 ? 0 : count + 1);
    });
 }

 useEffect(() => {
   if(count >= 100) stop();
 }, [count]);

【讨论】:

  • 如果在运行时卸载它会导致内存泄漏,因为 setInterval 不会被清理
猜你喜欢
  • 2020-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-14
  • 2019-04-01
相关资源
最近更新 更多