简体   繁体   English

将 useState 与 setInterval 内的 if 条件一起使用不起作用

[英]Using useState with an if conditional inside of setInterval isn't working

import { useState, useEffect } from "react";
import "./styles.css";

export default function App() {
  const [progress, setProgress] = useState(0);

  let progressTimer;

  function handleTime() {
    if (progress <= 100) {
      console.log("Progress: " + progress);
      setProgress((prevState) => (prevState += 10));
    } else {
      console.log("greater");
      clearInterval(progressTimer);
    }
  }

  function handlePlay() {
    console.log("Timer start");
    progressTimer = setInterval(handleTime, 1000);
  }

  useEffect(() => {
    handlePlay();
  });

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      {progress}
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Codesandbox Link 代码沙盒链接

Desired outcome: Go to 100, counting by 10 every 1 second.期望结果: Go 到 100,每 1 秒数 10。 Once you reach over 100, turn off the timer.一旦达到 100 以上,就关闭计时器。

Actual outcome: It just keeps going up and up, faster than 10 every 1 second.实际结果:它一直在上升,每 1 秒快于 10。

Issues问题

  1. The useEffect to start an interval has no dependency array so a new interval was started each time the component rendered.用于启动间隔的useEffect没有依赖数组,因此每次渲染组件时都会启动一个新的间隔。 This is what led to the bigger and bigger jumps.这就是导致越来越大的跳跃的原因。
  2. The progressTimer is redeclared each render cycle so there's no way to clear it.每个渲染周期都会重新声明progressTimer ,因此无法清除它。
  3. The check of the progress state is closed over in callback scope when passed to the setInterval callback.当传递给setInterval回调时, progress state 的检查在回调 scope 中结束。 You're only ever looking at the initial state value.您只会查看初始值 state。 In other words, it's a stale enclosure.换句话说,这是一个陈旧的外壳。
  4. Using prevState => (prevState += 10) in the functional state update actually mutates the previous state. All state mutations should be avoided.在功能性 state 更新中使用prevState => (prevState += 10)实际上会突变之前的 state。应避免所有 state 突变。

A Solution一个办法

  1. Add a dependency array to the useEffect so it runs once on component mount.useEffect添加一个依赖数组,以便它在组件挂载时运行一次。 Move the handlePlay logic into the effect callback so there are no external dependencies when mounting.handlePlay逻辑移动到效果回调中,以便在安装时没有外部依赖性。 Don't forget to return a cleanup function to clear any running intervals when the component unmounts.不要忘记返回清理 function 以清除组件卸载时的任何运行间隔。
  2. Store the progressTimer as a React ref so it's a stable reference.progressTimer存储为 React ref,因此它是一个稳定的参考。
  3. Unmix the state update with the side-effect of clearing the timer.解除 state 更新与清除计时器的副作用。 Use a second useEffect hook to check when the current progress value reaches 100.使用第二个useEffect挂钩来检查当前progress值何时达到 100。
  4. In the state updater just return the previous state plus 10, prevState => prevState + 10 as the next state value.在 state 更新器中,只返回之前的 state 加 10, prevState => prevState + 10作为下一个 state 的值。

Code代码

function App() {
  const [progress, setProgress] = useState(0);

  const progressTimer = useRef();

  function handleTime() {
    setProgress((prevState) => prevState + 10);
  }

  useEffect(() => {
    console.log("Progress: " + progress);
    if (progress >= 100) clearInterval(progressTimer.current);
  }, [progress]);

  useEffect(() => {
    console.log("Timer start");
    progressTimer.current = setInterval(handleTime, 1000);

    return () => clearInterval(progressTimer.current);
  }, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      {progress}
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

编辑 using-usestate-with-an-if-conditional-inside-of-setinterval-isnt-working(分叉)

setInterval with React, when called on the first render, will result in the interval callback having a stale closure of the stateful variable(s) after the first render.在第一次渲染时调用 React 的setInterval时,将导致间隔回调在第一次渲染后具有状态变量的陈旧关闭。

I'd use setTimeout instead, so that whenever the callback runs, it'll have scope of the most up-to-date state.我会改用setTimeout ,这样每当回调运行时,它就会有最新的 state 中的 scope。

 const { useState, useEffect } = React; function App() { const [progress, setProgress] = useState(0); function handleTime() { if (progress <= 100) { console.log("Progress: " + progress); setProgress((prevState) => (prevState += 10)); } else { console.log("greater"); } } useEffect(() => { const timerId = setTimeout(handleTime, 1000); return () => clearTimeout(timerId); }); return ( <div className="App"> <h1>Hello CodeSandbox</h1> {progress} <h2>Start editing to see some magic happen;</h2> </div> ). } ReactDOM,render(<App />. document.querySelector(';react'));
 <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div class='react'></div>

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

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