为什么我的Next.js组件会渲染两次?

28
这是一个组件,从 Firebase 存储中呈现数据并将其列出。该函数的任务是将从 Firebase 存储中提取的视频设置为 useState。这样我就可以调用视频并映射到新的组件中,该组件恰好是一个按钮列表。它表现得相当不错,问题在于该组件会渲染两次,第一次它不会将视频保存在状态中,而第二次则会。换句话说,该组件不等待视频保存在状态中,而只是简单地重新呈现自身,导致未显示带有视频标题的按钮列表。
// 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 capitalize from "../../../utils/capitalize";
import { PlayIcon } from "@heroicons/react/outline";

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

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

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

  // Check if path is empty

  function getVideos() {
    let items = [];
    listAll(reference).then((res) => {
      res.items.forEach(async (item) => {
        getDownloadURL(ref(storage, `training/${id}/${item.name}`)).then(
          (url) => {
            items.push({
              name: item.name.replace("-", " "),
              href: item.name,
              url,
            });
          }
        );
      });
    });
    setVideos(items);
  }

  useEffect(() => {
    getVideos();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id]);

  console.log(videos);

  return (
    <>
      <Seo
        title={`${capitalize(id)} Training - Dashboard`}
        description={`${capitalize(
          id
        )} training for all Every Benefits Agents.`}
      />
      <DashboardLayout>
        <h2>{capitalize(reference.name)}</h2>
        <section>
          <video controls controlsList="nodownload">
            {videoActive && <source src={videoActive} type="video/mp4" />}
          </video>
          <ul role="list" className="divide-y divide-gray-200 my-4">
            {videos.map((video, index) => (
              <button key={index} className="py-4 flex">
                <div className="w-full ml-3 flex flex-row justify-start items-center space-x-3">
                  <PlayIcon className="w-6 h-6 text-gray-600" />
                  <p className="text-sm font-medium text-gray-900">
                    {video.name}
                  </p>
                </div>
              </button>
            ))}

            {console.log("Component rendered")}
          </ul>
        </section>
      </DashboardLayout>
    </>
  );
}

export default withProtected(Video);

这是组件开始渲染的示例:

在此输入图片说明

有没有人知道为什么会发生这种情况?


你确定rerender只在Video组件中被调用了吗?也许是父组件强制重新渲染了。 - CubeStorm
@CubeStorm 有一个父组件,它是仪表板布局。它也被渲染,但与子组件一起。我注意到这一点是因为它们都渲染了两次。我将注销函数放在控制台日志中的原因是要检查它的精确位置。由于注销函数只在布局中呈现,所以我这样做。 - Diesan Romero
1
useEffect 调用 getVideos 来设置状态 - 状态更改会导致重新渲染。 - Sean W
@SeanW 我猜就是这样。当我在listAll内调用getDownloadUrl时会发生这种情况。我假设每次调用each时,状态都会重新渲染。问题是我需要来自两者的信息。listAll用于列出每个视频并在列表中显示它们,而getDownloadUrl用于访问Firebase URL,然后能够在编辑器中呈现视频。有没有解决方法? - Diesan Romero
我认为 setVideos(items) 被调用两次是因为您没有等待异步 API 调用。如果您将 setVideos(items) 立即移动到 res.items.forEach 之后会发生什么?那样有帮助吗?或者您可以尝试等待所有异步 API 调用,然后再使用 setVideos(items)。 - Mircea Matei
你会一直重新渲染组件,因为你在 useEffect 中更新了状态。如果你想避免这种情况,可以在服务器端获取视频数据。 - juliomalves
2个回答

82

我在这个帖子中找到了答案。 https://github.com/vercel/next.js/issues/35822

简而言之,这个问题是由于React 18严格模式引起的。你可以阅读关于React 18严格模式的新内容。

如果你没有使用严格模式,这个问题就不会发生。如果你不需要它,你可以在next.config.js文件中关闭React严格模式,如下所示。

const nextConfig = {
  reactStrictMode: false
}

module.exports = nextConfig


reactStrictMode 但没有版本18。应该会发生相同的情况吗? - Diesan Romero
4
需要注意的是,根据上面的链接,这仅影响开发模式:“这仅适用于开发模式,生产行为不变。” - NotoriousPyro

4
这是因为在React中使用了严格模式(strict mode),它用于检测组件级别的常见错误。为此,React会对单个组件进行两次渲染。第一次渲染是我们期望的结果,而另一次渲染是为了React严格模式。在开发阶段,您可能会遇到这个问题,尽管这并不是一个问题。这是为了在开发阶段获得更好的开发体验。在生产阶段,默认情况下不会出现这样的问题。
我们可以从以下链接了解更多信息:https://react.dev/reference/react/StrictMode 此外,如果您觉得这很烦人,您可以在next.config.js中禁用严格模式(我个人不建议禁用严格模式)。

const nextConfig = {
  reactStrictMode: false
}

module.exports = nextConfig

谢谢!


嘿,你有没有想法怎么处理这个API调用?因为渲染两次,最终会调用两次API?有什么建议吗? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接