如何防止在 useEffect 之前设置状态?

How to prevent set state before useEffect?

我有一个 React/Next 组件。该组件根据路由从 firebase 存储下载数据。例如,对于路由 http://localhost:3000/training/javascript 从 /training/javascript 路由器在 firebase 存储中获取数据的组件。

// ReactJS
import { useState, useEffect } from "react";

// NextJS
import { useRouter } from "next/router";

// Seo
import Seo from "../../../components/Seo";

// Hooks
import { withProtected } from "../../../hook/route";

// Components
import DashboardLayout from "../../../layouts/Dashboard";

// Firebase
import { getDownloadURL, getMetadata, listAll, ref } from "firebase/storage";
import { storage } from "../../../config/firebase";

// Utils
import prettysize from "prettysize";
import capitalize from "../../../utils/capitalize";
import { PlayIcon } from "@heroicons/react/outline";
import { async } from "@firebase/util";

function Video() {
  // States
  const [videos, setVideos] = useState([]);

  // Routing
  const router = useRouter();
  const { id } = router.query;

  // Reference
  const reference = ref(storage, `training/${id}`);

  useEffect(() => {
    const fetchData = async () => {
      let tempVideos = [];
      let completeVideos = [];
      const videos = await listAll(reference);

      videos.items.forEach((video) => {
        tempVideos.push(video);
      });

      tempVideos.forEach((video) => {
        getMetadata(ref(storage, video.fullPath)).then((metadata) => {
          completeVideos.push({
            name: metadata.name,
            size: prettysize(metadata.size),
          });
        });
      });

      tempVideos.forEach((video) => {
        getDownloadURL(ref(storage, video.fullPath)).then((url) => {
          completeVideos.forEach((completeVideo) => {
            if (completeVideo.name === video.name) {
              completeVideo.url = url;
            }
          });
        });
      });

      setVideos(completeVideos);
    };

    fetchData();
  }, [id]);

  console.log("Render", videos)
  return (
    <>
      <Seo
        title={`${capitalize(id)} Training - Dashboard`}
        description={`${capitalize(
          id
        )} training for all Every Benefits Agents.`}
      />
      <DashboardLayout>
        <h2>{capitalize(reference.name)}</h2>
        <ul>
          {videos.map((video) => {
            return (
              <li key={video.name}>
                <a href={video.url} target="_blank" rel="noopener noreferrer">
                  <PlayIcon />
                  {video.name}
                </a>
                <span>{video.size}</span>
              </li>
            );
          })}
        </ul>
      </DashboardLayout>
    </>
  );
}

export default withProtected(Video);

我有一个 useState,它应该是来自 firebase 的视频数组。我使用 useEffect 从 firebase 获取数据并提取所需的信息。一些 medatada 和 url.

一切都很好。信息被提取,并正确更新到状态。但是当状态更新时,屏幕上没有显示。

这是 console.log 视频状态更新,因此您可以看到它已正确更新。

你把异步代码和循环搞砸了,这应该对你有用:

 useEffect(() => {
    const fetchData = async () => {
      try {
        const videos = await listAll(reference);
        const completeVideos = await Promise.all(
          videos.items.map(async (video) => {
            const metadata = await getMetadata(ref(storage, video.fullPath));
            const url = await getDownloadURL(ref(storage, video.fullPath));
            return {
              name: metadata.name,
              size: prettysize(metadata.size),
              url,
            };
          })
        );
        setVideos(completeVideos);
      } catch (e) {
        console.log(e);
      }
    };
    fetchData();
  }, []);

Promise.all 接受一组承诺,并且 returns 一个承诺,一旦所有承诺都处于 fulfilled 状态,就会使用所有已解决值的数组来解决。当您想要对数组的多个元素执行异步操作(如 getMetaData 和 getDownloadURL)时,这很有用。您将使用 .map 而不是 .forEach,因为 map returns 是一个数组,而 forEach 不是。通过将 async 函数传递给 .map,因为 async 函数总是 returns 和 Promise,您基本上是在创建一个承诺数组。这就是您可以用来喂养 Promise.all 的东西。 就是这样,现在它只是等待所有异步调用完成,你可以 await 等待 Promise.all 来解决。