问题描述
我正在利用 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
挂钩实现 error
和 useQuery
属性。该组件可能类似于:
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
和一些重新排列之外,这与您所做的并没有太大的不同。希望从另一个角度看待这个话题至少会有所帮助。
我发现一些关于该主题的参考文献: