【问题标题】:React setTimeout calling orderReact setTimeout 调用顺序
【发布时间】:2022-01-04 23:07:56
【问题描述】:

我试图了解这个 React 组件是如何工作的(它是为了延迟其子组件的渲染):

function Delayed({ children, wait = 500 }) {
  const [show, setShow] = React.useState(false);

  React.useEffect(() => {
    console.log("effect");

    const timeout = window.setTimeout(() => {
      console.log("setTimeout", show);
      setShow(true);
    }, wait);

    return () => {
      console.log("cleanup");
      window.clearTimeout(timeout);
    };
  });

  console.log("render", show);
  return show === true ? children : null;
}

它会产生这样的输出:

1. render     false
2. effect 
3. setTimeout false
4. render     true
5. cleanup 
6. effect 
7. setTimeout true
8. render     true

直到最后一行,我都明白这个顺序。最后一个渲染日志来自哪里?布尔值此时已经 true,因此 setState 不应触发重新渲染,但它会以某种方式触发。我猜我遗漏了一些非常明显的东西。

代码沙盒:https://codesandbox.io/s/fancy-fire-6t380

【问题讨论】:

    标签: javascript reactjs settimeout


    【解决方案1】:

    使用useEffect,你“告诉 React 你的组件需要在渲染后做一些事情”(ref)。我在您的代码中添加了一些额外的日志记录,以获取时间:

    function time(msg) {
      return '[' + new Date().getTime() + '] ' + msg
    }
    
    function Delayed({ children, wait = 500 }) {
      const [show, setShow] = React.useState(false);
    
      React.useEffect(() => {
        console.log(time("effect"));
    
        const timeout = window.setTimeout(() => {
          console.log(time("setTimeout"), timeout, show);
          setShow(true);
        }, wait);
        console.log(time("created timeout"), timeout);
    
        return () => {
          console.log(time("cleanup"), timeout);
          window.clearTimeout(timeout);
        };
      });
    
      console.log(time("render"), show);
      return show === true ? children : null;
    }
    

    它输出:

    [1641339330296] render false
    [1641339330315] effect 
    [1641339330315] created timeout  16
    [1641339330816] setTimeout 16 false
    [1641339330818] render true
    [1641339330824] cleanup 16
    [1641339330825] effect 
    [1641339330825] created timeout 24
    [1641339331325] setTimeout 24 true
    [1641339331327] render true
    

    因此,效果的超时按预期在第一个render false 之后触发,但它在render true 之后再次触发,并设置另一个超时,这是不需要的。您可以使用if (!show) 来简单地防范这种情况,如下所示:

    function Delayed({ children, wait = 500 }) {
      const [show, setShow] = React.useState(false);
    
      React.useEffect(() => {
        console.log(time("effect"));
    
        if (!show) { // <--- THE CHANGE IS HERE
          const timeout = window.setTimeout(() => {
            console.log(time("setTimeout"), timeout, show);
            setShow(true);
          }, wait);
          console.log(time("created timeout"), timeout);
    
          return () => {
            console.log(time("cleanup"), timeout);
            window.clearTimeout(timeout);
          };
        }
      });
    
      console.log(time("render"), show);
      return show === true ? children : null;
    }
    

    输出:

    [1641339821991] render false
    [1641339822010] effect 
    [1641339822010] created timeout 16
    [1641339822510] setTimeout 16 false
    [1641339822513] render true
    [1641339822517] cleanup 16
    [1641339822518] effect 
    

    【讨论】:

    • 感谢您的回复。老实说,我不想阻止这种情况,此代码仅用于演示目的。我仍然没有得到最后的渲染日志。如果我们处于第二个(预期的)settimeout 中,show 已经为 true,并且我们再次将 state 设置为 true,它不应该触发渲染,对吗?那么这个最后的渲染是从哪里来的呢?
    • 嘿,你说得对。就好像从效果函数内部调用setShow,即使使用相同的值,也会触发重新渲染。我无法解释这一点。不过,也可能是解决您的问题的一个提示是,如果您使效果依赖于正确的事物,那么过度渲染就会消失:useEffect(..., [setShow, wait])
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-09
    • 1970-01-01
    • 2020-12-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-15
    • 1970-01-01
    相关资源
    最近更新 更多