【问题标题】:How to get setInterval in React to change a state variable?如何在 React 中获取 setInterval 以更改状态变量?
【发布时间】:2022-01-11 02:48:49
【问题描述】:

我希望以下代码在用户登录后从一个数字开始倒计时。

下面的代码在console.log中每秒显示简单的0,但似乎没有将状态变量secondsLeft设置为8,也没有将这个变量倒数。

const [secondsLeft, setSecondsLeft] = useState(0);

...

const handleButton = async () => {
    const response = await fetch('http://localhost:5001/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    });
    setUsername('');
    setPassword('');
    if (response.ok) {
        const data = await response.json();
        setCurrentUser(prev => ({ ...prev, ...data.user }));
        setMessage(`User: ${currentUser.firstName}`);
        setSecondsLeft(8);
        setInterval(() => {
            setSecondsLeft(prev => secondsLeft -1);
            console.log(secondsLeft);
        }, 1000);
    } else {
        setMessage('bad login');
    }
}

如何让 setInterval 每秒减少 secondsLeft 的值?

【问题讨论】:

  • 改用setSecondsLeft(prev => prev -1)怎么样?

标签: javascript reactjs setinterval


【解决方案1】:

这是 React 中一个非常常见的陷阱。可以理解,您似乎认为setSecondsLeft 立即生效,但事实并非如此。因此,secondsLeft 不会从 8 开始。还有一个问题是secondsLeft 在您的时间间隔内没有更新。这应该可以代替:

    setSecondsLeft(8);
    setInterval(() => {
        setSecondsLeft(prev => {
          console.log(prev);
          return prev - 1;
        });
    }, 1000);

【讨论】:

  • 有趣,介意解释一下为什么setSecondsLeft(prev => secondsLeft -1); 不起作用?
  • 因为在定义该函数时 secondsLeft 为 0 并且永远不会改变
【解决方案2】:

我尝试以传统方式做事,

useEffect(() => {
  setInterval(() => {
     console.log("update", st);
     setSt((prev) => prev + 1);
  }, 500);
}, []);

但这不起作用,因为在第一次渲染上运行useState 期间,setInterval 为自己保留了一个copy of prev,它会尝试一次又一次地更新,例如:setSt(prev => prev+1) 但因为 prev 是always = 1,因为它自己的闭包,值并没有真正更新。


然后我尝试使用 useCallback 钩子,它在依赖项更改时重新呈现 function 并因此提供 prev 作为依赖项,但遗憾的是,由于与上述相同的原因但通过不同的方式,这也很可能不起作用。

你可以在codesandbox的注释掉代码中看到我尝试的方式。


最后,我尝试创建一个setIntervalHack,它的工作原理类似于 setInterval,但不是真正的 setInterval。

这是我遵循的步骤。

  • 您可以想到setInterval as a recursive 函数,它在一段时间后一次又一次地运行,但缺点是我们无法将参数传递给它,因为更新会变得陈旧。李>
  • 因此,我将一个参数传递给 setIntervalHack,它描述了要更新的新状态。
  • 为了等待一段时间,我使用 promisesawait 再次运行递归,在调用递归函数 setIntervalHack 的参数中,我通过了 (currentState+1 )

这是我试图实现setInterval类似功能的方式

这里是代码https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/focus.js的链接

注意:你可以在codesandbox's browser中走/focus路由并打开控制台

PS:我对计数器进行了递增,而不是递减。您可以通过减少每次递归来做类似的事情

【讨论】:

    【解决方案3】:

    其实是可以的,但是你在控制台8中看到每次setIntervall回调运行,因为它只访问secondsLeft的初始值,并且在以后的更新中它不识别secondsLeft的更新。您可以运行以下示例并查看它每次运行 setInterval 回调时都会更新状态,但在控制台中您始终会看到 0。

    function App() {
      const [secondsLeft, setSeconds] = React.useState(0);
      let timerId = null;
      const handleClick = () => {
        setSeconds(8);
        timerId = setInterval(()=> {
          console.log(secondsLeft)
          setSeconds(prev => prev - 1);
        }, 1000)
      };
    
      React.useEffect(()=> {
        return ()=> clearInterval(timerId)
      }, [])
    
      return (
        <div>
          <h2>{secondsLeft}</h2>
          <button onClick={handleClick}>Start interval</button>
        </div>
      );
    }
    
    ReactDOM.render(React.createElement(App, {}), document.getElementById("root"));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-17
      • 2021-09-29
      • 2021-01-27
      • 2020-06-21
      • 1970-01-01
      • 1970-01-01
      • 2019-04-01
      相关资源
      最近更新 更多