反应 setInterval 行为

Hyd*_*erA 4 javascript setinterval ecmascript-6 reactjs

let updateTimer: number;

export function Timer() {
  const [count, setCount] = React.useState<number>(0);
  const [messages, setMessages] = React.useState<string[]>([]);

  const start = () => {
    updateTimer = setInterval(() => {
      const m = [...messages];
      m.push("called");
      setMessages(m);
      setCount(count + 1);
    }, 1000);
  };

  const stop = () => {
    clearInterval(updateTimer);
  };

  return (
    <>
      <div>{count}</div>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
      {messages.map((message, i) => (
        <p key={i}>{message}</p>
      ))}
    </>
  );
}
Run Code Online (Sandbox Code Playgroud)

代码示例:https : //codesandbox.io/s/romantic-wing-9yxw8?file=/ src/ App.tsx


该代码有两个按钮 - 开始和停止。

  • 开始调用 asetInterval并保存间隔 ID。定时器设置为 1 秒(1000 毫秒)。

  • 停止clearInterval在间隔 id 上调用 a 。

间隔 id 在组件外声明。

间隔回调函数增加一个计数器并向calledUI附加一条消息。

当我单击“开始”时,我希望计数器每秒递增一次,并called在按钮下方附加相应的消息。

实际发生的情况是,在单击“开始”时,计数器仅递增一次,called消息也是如此。

如果我再次单击“开始”,计数器会递增,然后重置回其先前的值。

如果我一直单击“开始”,计数器会不断增加并重置回其先前的值。

谁能解释这种行为?

Den*_*ash 7

您在时间间隔的回调中关闭count值。

因此,在使用 value 进行第一次状态更新后setState(0+1),您将拥有相同的countvalue 调用setState(0+1),不会触发另一个渲染。

使用没有闭包的使用先前状态值的功能更新

setCount((count) => count + 1);
Run Code Online (Sandbox Code Playgroud)

同样的原因messages

setMessages(prev => [...prev,"called"]);
Run Code Online (Sandbox Code Playgroud)
const start = () => {
  // should be a ref
  intervalId.current = setInterval(() => {
    setMessages((prev) => [...prev, "called"]);
    setCount((count) => count + 1);
  }, 1000);
};
Run Code Online (Sandbox Code Playgroud)

编辑tender-currying-vqi49


请注意 使用外部作用域变量而不是 的另一个可能的错误useRef,为此阅读了有关useRefvs 变量差异的信息


作为参考,这是一个简单的计数器切换示例:

function Component() {
  // use ref for consisent across multiple components
  // see /sf/ask/4021090811/#57444430
  const intervalRef = useRef();

  const [counter, setCounter] = useState(0);

  // simple toggle with reducer
  const [isCounterOn, toggleCounter] = useReducer((p) => !p, false);

  // handle toggle
  useEffect(() => {
    if (isCounterOn) {
      intervalRef.current = setInterval(() => {
        setCounter((prev) => prev + 1);
      }, 1000);
    } else {
      clearInterval(intervalRef.current);
    }
  }, [isCounterOn]);

  // handle unmount
  useEffect(() => {
    // move ref value into callback scope
    // to not lose its value upon unmounting
    const intervalId = intervalRef.current;
    return () => {
      // using clearInterval(intervalRef.current) may lead to error/warning
      clearInterval(intervalId);
    };
  }, []);

  return (
    <>
      {counter}
      <button onClick={toggleCounter}>Toggle</button>
    </>
  );
}
Run Code Online (Sandbox Code Playgroud)

编辑计数器切换

  • 啊,领先我7秒。我建议推动使用 React ref 并使用 `useEffect` 回调返回函数来清除卸载时的间隔,这样就不会出现“无法更新已卸载的 XXX”错误。 (4认同)