简体   繁体   English

React Hooks,useEffect 中的 setTimeout 直到结束才触发,因为 state 更新

[英]React Hooks, setTimeout in useEffect not triggering until end, because of state updates

Context :上下文

  • New messages are added (eg every two seconds, using setInterval).添加新消息(例如,每两秒,使用 setInterval)。
  • Messages have status, either old or new.消息具有旧的或新的状态。 Newly added messages have a 'new' flag.新添加的消息有一个“新”标志。
  • After every 5 seconds, all 'new' messages are designated 'old'.每 5 秒后,所有“新”消息都被指定为“旧”消息。 (setTimeout) (设置超时)

Problem :问题

  • Timeout is not triggering until the end.超时直到结束才会触发。 New messages are added, but they remain 'new' until all messages are added.添加了新消息,但在添加所有消息之前,它们仍然是“新的”。
  • I suspect that after every update the timeout is being reset/cleared and because the updates are occurring faster than the timeout, then the timeout callback never triggers in time (as a result only the final timeout get's triggered).我怀疑每次更新后超时都会被重置/清除,并且因为更新发生的速度比超时快,所以超时回调永远不会及时触发(因此只有最终的超时被触发)。

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]);
}

const defaultMessages = [
  {
    message: "message 1",
    new: false
  },
  {
    message: "message 2",
    new: false
  },
  {
    message: "message 3",
    new: true
  }
];

export default function App() {
  const [messages, setMessages] = React.useState(defaultMessages);
  const messagesRef = React.useRef(messages);

  messagesRef.current = messages;

  // add a new message every 2 seconds
  useInterval(() => {
    messages.length < 10 &&
      setMessages([
        ...messages,
        { message: `message ${messages.length + 1}`, new: true }
      ]);
  }, 2000);

  React.useEffect(() => {
    const timer = setTimeout(() => {
      // console.log("change all messages from new to old");
      const updateMessages = messagesRef.current.map(m => ({
        ...m,
        new: false
      }));
      setMessages([...updateMessages]);
    }, 5000); // if you change this to duration less than 2 seconds then it runs just fine
    return () => clearTimeout(timer); // removing the timer, calls it with every message update and seemingly ignores the timeout duration
  });

  return (
    <div className="App">
      {messages.map(m => (
        <div key={m.message}>
          {m.message}, status: {m.new ? "new" : "old"}
        </div>
      ))}
    </div>
  );
}

Example code : https://codesandbox.io/s/settimeout-resetting-with-updates-ufl3b示例代码https://codesandbox.io/s/settimeout-resetting-with-updates-ufl3b

Not sure how to approach this with React Hooks api.不知道如何使用 React Hooks api 来解决这个问题。 The timeouts need to persist, five seconds after every update.超时需要在每次更新后持续 5 秒。 Where as right now every timeout seemed to be canceled or queued by the one that comes after it.现在,每个超时似乎都被之后的超时取消或排队。 I'm puzzled.我很困惑。

Thank you!谢谢!

Well, the main problem that I noticed is clearing the timeout on next render , meaning, if you render faster enough, you actually canceling the timeout callback instead of running it.好吧,我注意到的主要问题是清除下一次渲染的超时,这意味着,如果您渲染得足够快,您实际上会取消超时回调而不是运行它。

 React.useEffect(() => {
    const timer = setTimeout(() => {});
    // will clear the timeout on ***next*** render!
    return () => clearTimeout(timer);
  });

So after fixing it, and using functional updates instead of reference, seems like this code works:因此,在修复它并使用功能更新而不是参考之后,这段代码似乎可以工作:

export default function App() {
  const [messages, setMessages] = React.useState(defaultMessages);

  // add a new message every 2 seconds
  useInterval(() => {
    messages.length < 10 &&
      setMessages(prev => [
        ...prev,
        { message: `message ${messages.length + 1}`, new: true }
      ]);
  }, 2000);

  React.useEffect(() => {
    console.log("rendered");
    setTimeout(() => {
      setMessages(prev =>
        prev.map(m => ({
          ...m,
          new: false
        }))
      );
    }, 3000);
  });

  return (
    <div className="App">
      {messages.map(m => (
        <div key={m.message}>
          {m.message}, status: {m.new ? "new" : "old"}
        </div>
      ))}
    </div>
  );
}

使用更新编辑 setTimeout 重置

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

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