Next.JS 静态站点生成和客户端 Apollo 获取

问题描述

我正在利用 Next.JS SSG 来提高我网站的加载速度。关键是我需要在客户端获取一些数据,因为它属于登录用户

假设我们有一个类似 YouTube 的网站,所以我们会有 Video page侧边栏组件,即 VideoRelated

我会使用 Video page 处的 VideoQuery 生成 getStaticProps 静态。它还会获取(客户端)登录用户的完成状态,所以它会是这样的:

export const VideoRelatedList = ({ videoId,relatedVideos }) => {
  const { data } = useViewerVideoStatusQuery({ variables: { videoId } });
  const relatedVideosViewerStatus = data?.videos;

  const videos = React.useMemo(() => {
    if (!relatedVideosViewerStatus) return relatedVideos;

    return relatedVideos.map((relatedVideo,index) => {
      const viewerHasCompleted = relatedVideosViewerStatus[index]?.id === relatedVideo.id && relatedVideosViewerStatus[index]?.viewerHasCompleted;
      return { ...relatedVideo,viewerHasCompleted };
    });
  },[relatedVideosViewerStatus,relatedVideos]);

  return (
    <ol>
      {videos.map(({ id,name,viewerHasCompleted },index) => (
        <li key={id}>
          {name} - {viewerHasCompleted && 'COMPLETED!'}
        </li>
      ))}
    </ol>
  );
};

合并这两种数据的最佳方式是什么?

目前,我正在做的是通过使用 React.memo 将两者结合起来,但我不确定这是否是最佳实践,或者是否有更好的方法来实现我想要的。

解决方法

tl;dr 这似乎是一个不错的方法,您可能不需要 useMemo

Next.js 文档有关于客户端数据获取的 a very brief blurb。本质上,它看起来像您正在做的事情,只是他们宣传了他们的 swr 库以用于通用数据提取。

由于您使用的是 GraphQL 和 Apollo,因此我将假设 useViewerVideoStatusQuery 扩展了 useQuery hook from @apollo/client

你提到:

目前,我正在做的是通过使用 React.memo 将两者结合起来,但我不确定这是否是最佳实践,或者是否有更好的方法来实现我想要的。

请耐心等待,我将尝试确认我对您的用例的理解。如果我没看错,看起来 VideoRelatedList 组件大部分是完全静态的,而且看起来这就是你所做的——如果额外的数据没有加载,它只使用通过 SSG 构建的 props 传递的数据。这很好,应该给你最快的 First Contentful Paint (FCP) & Largest Contentful Paint (LCP)

如果没有额外的数据,组件看起来就像:

export const VideoRelatedList = ({ videoId,relatedVideos }) => {
  return (
    <ol>
      {relatedVideos.map(({ id,name,viewerHasCompleted },index) => (
        <li key={id}>
          {name} - {viewerHasCompleted && 'COMPLETED!'}
        </li>
      ))}
    </ol>
  );
};

现在您另外想要合并用户数据,以提供告诉用户哪些视频是完整的功能。您是通过在页面最初使用 Apollo 呈现后获取数据来实现的。

我要做的主要更改(除非 relatedVideos 数组很大或者我遗漏了一些昂贵的操作)是删除 useMemo,因为考虑到它可能不会在这里节省大量成本那个performance optimizations aren't free

您还可以从 loading 挂钩实现 erroruseQuery 属性。该组件可能类似于:

export const VideoRelatedList = ({ videoId,relatedVideos }) => {
  const { data,loading,error } = useViewerVideoStatusQuery({ variables: { videoId } });

  if (loading) {
    <ol>
      {relatedVideos.map(({ id,index) => (
        <li key={id}>
          {name} - ...checking status...
        </li>
      ))}
    </ol>
  }

  if (error || !data.videos) {
    return (
      <ol>
        {videos.map(({ id,index) => (
          <li key={id}>
            {name}
          </li>
        ))}
      </ol>
    );
  }

  const videos = relatedVideos.map((relatedVideo,index) => {
      const viewerHasCompleted = relatedVideosViewerStatus[index]?.id === relatedVideo.id && relatedVideosViewerStatus[index]?.viewerHasCompleted;
      return { ...relatedVideo,viewerHasCompleted };
    });

  return (
    <ol>
      {videos.map(({ id,index) => (
        <li key={id}>
          {name} - {viewerHasCompleted && 'COMPLETED!'}
        </li>
      ))}
    </ol>
  );
};

再说一次,除了没有 useMemo 和一些重新排列之外,这与您所做的并没有太大的不同。希望从另一个角度看待这个话题至少会有所帮助。

我发现一些关于该主题的参考文献: