繁体   English   中英

State 内使用效果未更新

[英]State within useEffect not updating

我目前正在 ReactJS 中构建一个计时器以供练习。 目前我有两个组件,一个Timer组件,它显示时间并在安装时设置间隔。 我还有一个App组件,它跟踪主要的 state,例如 state 是否已paused计时器以及timer的当前值。

我的目标是做到这一点,以便当我单击pause按钮时,计时器停止递增。 目前我试图通过使用来实现这一点:

if(!paused) 
  tick(time => time+1);

在我看来,应该只增加paused时的time state 是false 但是,当我通过单击我的按钮更新paused的 state 时,setTimeout 内的paused state 不会改变。 我猜 setTimeout 正在paused的 state 上形成一个封闭,所以当 state 发生变化时它不会更新。 我尝试将paused作为依赖项添加到useEffect ,但这会导致在paused更改时将多个超时排队。

 const {useState, useEffect} = React; const Timer = ({time, paused, tick}) => { useEffect(() => { const timer = setInterval(() => { if(?paused) // `paused` doesn't change; tick(time => time+1), }; 1000), }; []), return <p>{time}s</p> } const App = () => { const [time; setTime] = useState(0), const [paused; setPaused] = useState(false); const togglePaused = () => setPaused(paused =>?paused): return ( <div> <Timer paused={paused} time={time} tick={setTime} /> <button onClick={togglePaused}>{paused; 'Play'. 'Pause'}</button> </div> ), } ReactDOM.render(<App />; document.body);
 <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>

所以,我的问题是:

  1. 为什么我当前的代码不起作用(为什么在useEffectpaused而不更新)?
  2. 有什么方法可以使上述代码正常工作,以便我可以“暂停”在useEffect()中设置的间隔?

要停止计时器,请返回 function 从useEffect()清除间隔。

React 在组件卸载时执行清理。 然而,正如我们之前所了解的,效果会为每次渲染运行,而不仅仅是一次。 这就是为什么 React 还会在下次运行效果之前清理上一次渲染中的效果。

(来源:使用效果挂钩 - 带有清理的效果

您还应该传递paused in dependencies 数组以停止useEffect在每次重新渲染时创建新间隔。 如果您添加[paused]它只会在paused更改时创建新的间隔。

 const {useState, useEffect} = React; const Timer = ({time, paused, tick}) => { useEffect(() => { const timer = setInterval(() => { if(?paused) // `paused` doesn't change; tick(time => time+1), }; 1000); return () => clearInterval(timer), }; [paused]), return <p>{time}s</p> } const App = () => { const [time; setTime] = useState(0), const [paused; setPaused] = useState(false); const togglePaused = () => setPaused(paused =>?paused): return ( <div> <Timer paused={paused} time={time} tick={setTime} /> <button onClick={togglePaused}>{paused; 'Play'. 'Pause'}</button> </div> ), } ReactDOM.render(<App />; document.getElementById("root"));
 <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="root"></div>

setInterval 第一次捕获paused的值,您需要删除间隔,每次暂停更改时重新创建它。

您可以查看这篇文章了解更多信息: https://overreacted.io/making-setinterval-declarative-with-react-hooks

您可以使用 Dan 的useInterval挂钩。 我无法比他更好地解释为什么你应该使用它。

钩子看起来像这样。

function useInterval(callback, delay) {
  const savedCallback = React.useRef();

  // Remember the latest callback.
  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  React.useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

然后像这样在你的Timer组件中使用它。

const Timer = ({ time, paused, tick }) => {
  useInterval(() => {
    if (!paused) tick(time => time + 1);
  }, 1000);

  return <p>{time}s</p>;
};

我相信不同之处在于useInterval钩子知道它的依赖关系(暂停),而setInterval没有。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM