React Hooks,useEffect 中的 setTimeout 直到结束才触发,因为状态更新 [英] React Hooks, setTimeout in useEffect not triggering until end, because of state updates

查看:560
本文介绍了React Hooks,useEffect 中的 setTimeout 直到结束才触发,因为状态更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

上下文:

  • 添加新消息(例如每两秒,使用 setInterval).
  • 消息有状态,可以是旧的也可以是新的.新添加的消息带有新"标志.
  • 每 5 秒后,所有新"消息都会被指定为旧"消息.(setTimeout)

问题:

  • 直到结束才会触发超时.添加了新消息,但在添加所有消息之前,它们将保持新"状态.
  • 我怀疑每次更新后超时都会被重置/清除,并且因为更新发生得比超时快,所以超时回调永远不会及时触发(因此只有最终超时被触发).
<预><代码>函数 useInterval(回调,延迟){const savedCallback = React.useRef();//记住最新的回调.React.useEffect(() => {savedCallback.current = 回调;}, [打回来]);//设置间隔.React.useEffect(() => {功能滴答(){saveCallback.current();}如果(延迟!== 空){让 id = setInterval(tick, delay);返回 () =>clearInterval(id);}}, [延迟]);}const defaultMessages = [{消息:消息1",新:假},{消息:消息2",新:假},{消息:消息3",新:真实}];导出默认函数 App() {const [messages, setMessages] = React.useState(defaultMessages);const messagesRef = React.useRef(messages);messagesRef.current = 消息;//每 2 秒添加一条新消息useInterval(() => {消息长度<10 & &设置消息([...消息,{ message: `message ${messages.length + 1}`, new: true }]);}, 2000);React.useEffect(() => {const timer = setTimeout(() => {//console.log(将所有消息从新消息更改为旧消息");const updateMessages = messagesRef.current.map(m => ({...米,新:假}));setMessages([...updateMessages]);}, 5000);//如果将其更改为小于 2 秒的持续时间,则它运行得很好返回 () =>清除超时(定时器);//移除计时器,在每次消息更新时调用它,并且似乎忽略了超时持续时间});返回 (

{messages.map(m => (<div key={m.message}>{m.message},状态:{m.new ?新":旧"}

))}

);}

示例代码:

Context:

  • New messages are added (e.g. every two seconds, using 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'. (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

Not sure how to approach this with React Hooks api. The timeouts need to persist, five seconds after every update. 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>
  );
}

这篇关于React Hooks,useEffect 中的 setTimeout 直到结束才触发,因为状态更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆