【问题标题】:React setInterval Behavior反应 setInterval 行为
【发布时间】:2021-04-05 21:53:39
【问题描述】:
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>
      ))}
    </>
  );
}

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


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

  • Start 调用 setInterval 并保存间隔 id。计时器设置为 1 秒(1000 毫秒)。

  • Stop 在间隔 id 上调用 clearInterval

区间id在组件外声明。

间隔回调函数增加一个计数器并将called 消息附加到 UI。

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

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

如果我再次单击“开始”,计数器会增加并随后重置为之前的值。

如果我继续点击“开始”,计数器会继续递增并重置为之前的值。

谁能解释这种行为?

【问题讨论】:

    标签: javascript reactjs ecmascript-6 setinterval


    【解决方案1】:

    您在间隔回调中的count 值上有closure

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

    使用functional updates,它使用之前没有闭包的状态值:

    setCount((count) => count + 1);
    

    messages 的相同原因:

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


    另一个可能的错误通知 使用外部范围变量而不是 useRef,为此请阅读 useRef vs variable differences


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

    function Component() {
      // use ref for consisent across multiple components
      // see https://stackoverflow.com/questions/57444154/why-need-useref-to-contain-mutable-variable-but-not-define-variable-outside-the/57444430#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>
        </>
      );
    }
    

    【讨论】:

    • 啊,比我快 7 秒。我建议推动使用 react ref 并使用 useEffect 回调返回函数来清除卸载时的间隔,因此不会出现“无法更新卸载的 XXX”错误。
    • 同意,应该将setInterval() 包裹在useEffect() 中。通过isCounting等状态触发效果,点击开始和停止按钮时设置isCounting
    猜你喜欢
    • 2021-07-31
    • 2021-08-20
    • 2021-06-02
    • 2019-05-27
    • 2021-10-03
    • 2020-12-09
    • 2021-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多