【问题标题】:React + Typescript: setTimeout in For Loop is not populating array consecutively, or at allReact + Typescript:For Loop 中的 setTimeout 没有连续填充数组,或者根本没有填充数组
【发布时间】:2022-01-26 22:43:41
【问题描述】:

我正在构建一个函数,使随机汽车的图像在屏幕上显示动画,并且我想用 setTimeout 错开“carsLeft”数组的人口......(我最终将随机化延迟时间)。

在我尝试使用 setTimeout 之前一切正常。使用下面的代码,没有显示任何汽车。 Console.log 显示“carsLeft”数组未填充。当我删除 setTimeout 时,会显示所有汽车(当然是一次)。我已经尝试过 IIDE 仍然没有运气。卡在这个上面一段时间了,请帮忙!

function Traffic() {
  let carsLeft: any = [];
  const generateCarLeft = () => {
    for (let i = 0; i < 5; i++) {
      setTimeout(() => {
        carsLeft.push(
          <CarLeft key={i} className="car__left">
            <img
              src={carListDay[Math.floor(Math.random() * carListDay.length)]}
              alt=""
            />
          </CarLeft>
        );
      }, 3000);
    }
  };

  generateCarLeft();
  return <div className="traffic__container">{carsLeft}</div>;
}

export default Traffic;

【问题讨论】:

    标签: reactjs typescript for-loop settimeout


    【解决方案1】:

    如果你想通过循环生成元素并在组件挂载后发生是使用React.useEffect钩子,例如

    React.useEffect(() => {
      generateCarsLeft()
    }, [])
    

    并且还使用carsLeft 作为状态将解决问题。

    【讨论】:

      【解决方案2】:

      如果没有 state 和 setTimeout 它会起作用,因为在第一次渲染(挂载)之前所有数据都可用。

      但是如果你在第一个渲染列表中使用是空的并且在 setTimeout 之后它会更新变量但反应不知道变量被更新你必须使用状态来解决这个问题。

      function Traffic() {
        const [carsLeft, setCarsLeft] = useState([]);
        const generateCarLeft = () => {
          for (let i = 0; i < 5; i++) {
            setTimeout(() => {
              setCarsLeft((items) => [
                ...items,
                <CarLeft key={i} className="car__left">
                  <img
                    src={carListDay[Math.floor(Math.random() * carListDay.length)]}
                    alt=""
                  />
                </CarLeft>,
              ]);
            }, 3000);
          }
        };
      
        generateCarLeft();
        return <div className="traffic__container">{carsLeft}</div>;
      }
      

      【讨论】:

      • 因为for是同步的,所以3s什么都不做,然后一次性加5辆车。
      【解决方案3】:
      1. React 组件本质上只是一个函数,因此如果您在该函数中声明一个变量,则每次调用它时都会重新创建它。如果您希望任何值在调用之间保持不变,则必须将其保存在其他地方。在 React 中,“某处”是状态。

      2. 除非 React 检测到状态变化,否则它甚至不会重新渲染组件。

      到目前为止,您的组件被调用一次,3 秒后,所有 5 个新元素立即添加到数组中,但组件没有重新渲染,因为 React 没有跟踪此更改。我会给你一个例子来说明你是如何让它工作的,但我建议你更多地了解 React 的状态和生命周期的概念。

      function Traffic() {
       [carsLeft, setCarsLeft] = React.useState([]);
      
       React.useEffect(()=>{
        if(carsLeft.length > 4) return;
         setTimeout(() => {
           setCarsLeft( cl => [...cl,  
            <CarLeft key={i} className="car__left">
              <img
                src={carListDay[Math.floor(Math.random() * carListDay.length)]}
                alt=""/>]
            );
           }, 3000);
      
         })
        return <div className="traffic__container">{carsLeft}</div>;
      }
      

      【讨论】:

      • 我想通了,我必须将 IIFE 与 State 结合使用。现在发布答案。
      【解决方案4】:

      这就是你要找的东西

      const list = ["Hello", "how", "are", "you"];
      function App() {
        const [myList, updateList] = useState([])
      
        useEffect(() =>{
          for (let i=0; i<list.length; i++) {
            setTimeout(() => {
              updateList((prvList) => ([...prvList, list[i]]));
            }, 1000 * i) // * i is important
          }
        }, []);
        
        return (
          <div>
            {
              myList.map(li => (<div key={li}>{li}</div>))
            }
          </div>
        );
      }
      

      【讨论】:

        【解决方案5】:

        即使它确实运行了,它也不会按照你想要的方式运行。因为for 循环非常快,所以您需要等待 3 秒才能一次获得 5 辆车。

        你需要摆脱循环。您需要将所有副作用包装在 useEffect 挂钩中,并将所有持久变量(如 carsLeft)包装在 useState 挂钩中。

        export default function Traffic() {
          const [carsLeft, setCarsLeft] = useState<Array<ReactNode>>([]);
          const timeout = useRef<number>();
        
          useEffect(() => {
            timeout.current = setTimeout(() => {
              if (carsLeft.length < 5)
                setCarsLeft([
                  ...carsLeft,
                  <CarLeft key={carsLeft.length} className="car__left">
                    <img
                      src={carListDay[Math.floor(Math.random() * carListDay.length)]}
                      alt=""
                    />
                  </CarLeft>
                ]);
              else clearTimeout(timeout.current);
            }, 3000);
        
            return () => {
              clearTimeout(timeout.current);
            };
          }, [carsLeft]);
        
          return <div className="traffic__container">{carsLeft}</div>;
        }
        

        Codesandbox 演示:https://codesandbox.io/s/naughty-driscoll-6k6u8?file=/src/App.tsx

        【讨论】:

        • 我喜欢这个解决方案,谢谢。 =)
        【解决方案6】:

        我已经解决了所有问题,并且一切正常。我使用@Summer 的方法删除了 for 循环,并让 useEffect + State 更改创建了自己的无限循环。我使用 setInterval 方法代替了 setTimeout。 Summer 解决方案的一个问题是使用“carsOnTheLeft.length”生成键值。这导致多个键具有相同的值并导致一些问题。为了解决这个问题,我创建了一个新的状态“carLeftIndex”来在每次重新渲染时生成一个唯一的键。由于 React 的“onAnimationEnd()”触发了“handleRemoveItem”函数,动画完成后生成的对象将从状态数组中删除。这是我的工作代码,谢谢你的回答!

        const getRandomNumber = (min: number, max: number) => {
          return Math.random() * (max - min) + min;
        };
        
        const carListDay = [car_1, car_2, car_3, car_4, car_5, car_6];
        
        function Traffic() {
          const [carsOnTheLeft, setCarsOnTheLeft] = useState<any>([]);
          const timeout = useRef<any>();
          const [carLeftIndex, setCarLeftIndex] = useState(0);
        
          useEffect(() => {
            const getRandomNumberToString = (min: number, max: number) => {
              const result = Math.random() * (max - min) + min;
              return result.toString();
            };
            setCarLeftIndex(carLeftIndex + 1);
        
            const CarLeft = styled.div`
              animation-duration: ${getRandomNumberToString(3, 10)}s;
            `;
        
            timeout.current = setInterval(() => {
              getRandomNumberToString(2, 9);
          if (carsOnTheLeft.length < 15)
                setCarsOnTheLeft([
                  ...carsOnTheLeft,
                  <CarLeft
                    key={carLeftIndex}
                    className="car__left"
                    onAnimationEnd={(e) => handleRemoveItem(e)}
                  >
                    <img
                      src={carListDay[Math.floor(Math.random() * carListDay.length)]}
                      alt=""
                    />
                  </CarLeft>,
                ]);
         else clearTimeout(timeout.current);
            }, getRandomNumber(100, 5000));
            console.log(carsOnTheLeft);
            console.log(carLeftIndex);
            const handleRemoveItem = (e: any) => {
              setCarsOnTheLeft((carsOnTheLeft: any) =>
                carsOnTheLeft.filter((key: any, i: any) => i !== 0)
              );
            };
        
            return () => {
              clearTimeout(timeout.current);
            };
          }, [carsOnTheLeft]);
        
          return <div className="traffic__container">{carsOnTheLeft}</div>;
        

        【讨论】:

          猜你喜欢
          • 2021-02-03
          • 2020-07-14
          • 1970-01-01
          • 2017-03-30
          • 1970-01-01
          • 2019-11-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多