React Query with Next.js App Router for Advanced Server Rendering -- 知识铺
64 1 2
高级服务器渲染 |使用 Next.js App Router 响应查询
在本指南中,您将学习如何将 React Query 与服务器渲染结合使用。
什么是服务器渲染?
服务器渲染是在服务器上生成初始 HTML,以便用户在页面加载时立即看到一些内容。这可以通过两种方式完成:
服务器端渲染 (SSR):每次请求页面时在服务器上生成 HTML。
静态站点生成 (SSG):在构建时预生成 HTML 或使用先前请求的缓存版本。
为什么服务器渲染有用?
使用客户端渲染时,流程如下所示:
- 加载没有内容的标记。
- 加载 JavaScript。
- 通过查询获取数据。
在用户看到任何内容之前,这需要至少三次服务器往返。
服务器渲染简化了这个过程:
- 加载带有内容和初始数据的标记。
- 加载 JavaScript。
第 1 步完成后,用户就会看到内容,第 2 步后页面将变为交互式。初始数据已包含在标记中,因此最初无需获取额外的数据!
这与 React 查询有何关系?
使用 React Query,您可以在服务器上生成/渲染标记之前预取数据,然后使用客户端上的数据以避免新的获取。
现在如何实施这些步骤..
初始设置
使用 React Query 的第一步始终是创建一个 queryClient 并将应用程序包装在 <QueryClientProvider>
中。在进行服务器渲染时,在 React 状态下在应用程序内部创建 queryClient 实例非常重要(实例引用也可以正常工作)。
这确保了数据不会在不同的用户和请求之间共享,同时每个组件生命周期仍然只创建一次 queryClient。
商店.tsx:
<span>"</span><span>use client</span><span>"</span><span>;</span>
<span>import</span> <span>{</span> <span>useState</span> <span>}</span> <span>from</span> <span>"</span><span>react</span><span>"</span><span>;</span>
<span>import</span> <span>{</span> <span>QueryClient</span><span>,</span> <span>QueryClientProvider</span> <span>}</span> <span>from</span> <span>"</span><span>@tanstack/react-query</span><span>"</span><span>;</span>
<span>export</span> <span>default</span> <span>function</span> <span>Store</span><span>({</span> <span>children</span> <span>}:</span> <span>{</span> <span>children</span><span>:</span> <span>React</span><span>.</span><span>ReactNode</span> <span>})</span> <span>{</span>
<span>const</span> <span>[</span><span>queryClient</span><span>]</span> <span>=</span> <span>useState</span><span>(</span>
<span>()</span> <span>=></span>
<span>new</span> <span>QueryClient</span><span>({</span>
<span>defaultOptions</span><span>:</span> <span>{</span>
<span>queries</span><span>:</span> <span>{</span>
<span>staleTime</span><span>:</span> <span>5</span> <span>*</span> <span>60</span> <span>*</span> <span>1000</span><span>,</span>
<span>},</span>
<span>},</span>
<span>}),</span>
<span>);</span>
<span>return </span><span>(</span>
<span><</span><span>QueryClientProvider</span> <span>client</span><span>=</span><span>{</span><span>queryClient</span><span>}</span><span>></span>
<span>{</span><span>children</span><span>}</span>
<span><</span><span>/QueryClientProvider</span><span>>
</span> <span>);</span>
<span>}</span>
布局.tsx:
<span>import</span> <span>Store</span> <span>from</span> <span>"</span><span>@/provider/store</span><span>"</span><span>;</span>
<span>export</span> <span>default</span> <span>async</span> <span>function</span> <span>RootLayout</span><span>({</span>
<span>children</span><span>,</span>
<span>}:</span> <span>{</span>
<span>children</span><span>:</span> <span>React</span><span>.</span><span>ReactNode</span><span>;</span>
<span>})</span> <span>{</span>
<span>return </span><span>(</span>
<span><</span><span>html</span> <span>lang</span><span>=</span><span>"</span><span>en</span><span>"</span><span>></span>
<span><</span><span>body</span><span>></span>
<span><</span><span>Store</span><span>></span><span>{</span><span>children</span><span>}</span><span><</span><span>/Store</span><span>>
</span> <span><</span><span>/body</span><span>>
</span> <span><</span><span>/html</span><span>>
</span> <span>);</span>
<span>}</span>
使用水合 API
只需进行更多设置,您就可以使用 queryClient 在预加载阶段预取查询,将该 queryClient 的序列化版本传递到应用程序的渲染部分,并在那里重用它。这避免了前面提到的缺点。
水合作用.tsx:
<span>import</span> <span>{</span>
<span>QueryClient</span><span>,</span>
<span>dehydrate</span><span>,</span>
<span>HydrationBoundary</span><span>,</span>
<span>}</span> <span>from</span> <span>"</span><span>@tanstack/react-query</span><span>"</span><span>;</span>
<span>import</span> <span>getData</span> <span>from</span> <span>"</span><span>@/api/getData</span><span>"</span><span>;</span>
<span>import</span> <span>React</span> <span>from</span> <span>"</span><span>react</span><span>"</span><span>;</span>
<span>export</span> <span>default</span> <span>async</span> <span>function</span> <span>Hydration</span><span>({</span>
<span>children</span><span>,</span>
<span>}:</span> <span>{</span>
<span>children</span><span>:</span> <span>React</span><span>.</span><span>ReactNode</span><span>;</span>
<span>})</span> <span>{</span>
<span>const</span> <span>queryClient</span> <span>=</span> <span>new</span> <span>QueryClient</span><span>({</span>
<span>defaultOptions</span><span>:</span> <span>{</span>
<span>queries</span><span>:</span> <span>{</span>
<span>staleTime</span><span>:</span> <span>5</span> <span>*</span> <span>60</span> <span>*</span> <span>1000</span><span>,</span> <span>// this sets the cache time to 5 minutes</span>
<span>},</span>
<span>},</span>
<span>});</span>
<span>await</span> <span>Promise</span><span>.</span><span>all</span><span>([</span>
<span>queryClient</span><span>.</span><span>prefetchQuery</span><span>({</span>
<span>queryKey</span><span>:</span> <span>[</span><span>"</span><span>profiles</span><span>"</span><span>,</span> <span>"</span><span>user</span><span>"</span><span>],</span>
<span>queryFn</span><span>:</span> <span>()</span> <span>=></span> <span>getData</span><span>(</span><span>"</span><span>profiles</span><span>"</span><span>),</span>
<span>}),</span>
<span>queryClient</span><span>.</span><span>prefetchQuery</span><span>({</span>
<span>queryKey</span><span>:</span> <span>[</span><span>"</span><span>permissions</span><span>"</span><span>,</span> <span>"</span><span>user</span><span>"</span><span>],</span>
<span>queryFn</span><span>:</span> <span>()</span> <span>=></span> <span>getData</span><span>(</span><span>"</span><span>permissions</span><span>"</span><span>),</span>
<span>}),</span>
<span>]);</span>
<span>return </span><span>(</span>
<span><</span><span>HydrationBoundary</span> <span>state</span><span>=</span><span>{</span><span>dehydrate</span><span>(</span><span>queryClient</span><span>)}</span><span>></span>
<span>{</span><span>children</span><span>}</span>
<span><</span><span>/HydrationBoundary</span><span>>
</span> <span>);</span>
<span>}</span>
新的layout.tsx:
<span>import</span> <span>Hydration</span> <span>from</span> <span>"</span><span>@/provider/hydration</span><span>"</span><span>;</span>
<span>import</span> <span>Store</span> <span>from</span> <span>"</span><span>@/provider/store</span><span>"</span><span>;</span>
<span>export</span> <span>default</span> <span>async</span> <span>function</span> <span>RootLayout</span><span>({</span>
<span>children</span><span>,</span>
<span>}:</span> <span>{</span>
<span>children</span><span>:</span> <span>React</span><span>.</span><span>ReactNode</span><span>;</span>
<span>})</span> <span>{</span>
<span>return </span><span>(</span>
<span><</span><span>html</span> <span>lang</span><span>=</span><span>"</span><span>en</span><span>"</span><span>></span>
<span><</span><span>body</span><span>></span>
<span><</span><span>Store</span><span>></span>
<span><</span><span>Hydration</span><span>></span><span>{</span><span>children</span><span>}</span><span><</span><span>/Hydration</span><span>>
</span> <span><</span><span>/Store</span><span>>
</span> <span><</span><span>/body</span><span>>
</span> <span><</span><span>/html</span><span>>
</span> <span>);</span>
<span>}</span>
一般来说,这些是额外的步骤:
-
使用
new QueryClient(options)
创建一个常量 queryClient (设置 staleTime 很重要,否则,React Query 将在数据到达客户端后立即重新获取数据)。 -
对要预取的每个查询使用
await queryClient.prefetchQuery(...)
。如果可能,请使用
await Promise.all(...)
并行获取查询。 -
没有预取的查询是可以的。这些不会由服务器渲染;相反,它们将在应用程序交互后在客户端上获取。这对于仅在用户交互后显示的内容或页面下方的内容非常有用,以避免阻止更关键的内容。
-
用
<HydrationBoundary state={dehydrate(queryClient)}>
包裹你的树,其中 deHydratedState 来自框架加载器。
一个重要的细节
当使用 React Query 进行服务器渲染时,该过程实际上涉及三个 queryClient 实例:
-
预加载阶段:
在渲染之前,会创建一个queryClient来预取数据。
必要的数据被获取并存储在该 queryClient 中。
-
服务器渲染阶段:
一旦数据被预取,它就会被脱水(序列化)并发送到服务器渲染进程。
在服务器上创建一个新的 queryClient 并注入脱水数据。
这可确保服务器为客户端生成完全填充的 HTML。
-
客户端渲染阶段:
脱水后的数据被传递给客户端。
在客户端上创建另一个 queryClient 并用数据重新水化。
这确保客户端以相同的数据启动,保持一致性并跳过初始数据获取。
这确保所有进程都以相同的数据启动,因此它们可以返回相同的标记。
然后使用 useQuery()
挂钩,您可以像平常一样使用预取查询,并且数据将在预加载阶段预取。这意味着当您使用 useQuery()
获取组件中的数据时,React Query 将在组件渲染之前自动处理该数据的预取。这有助于确保数据在需要时可用,从而有助于提高应用程序的性能。
<span>"</span><span>use client</span><span>"</span><span>;</span>
<span>import</span> <span>getData</span> <span>from</span> <span>"</span><span>@/api/getData</span><span>"</span><span>;</span>
<span>import</span> <span>{</span> <span>useQuery</span> <span>}</span> <span>from</span> <span>"</span><span>@tanstack/react-query</span><span>"</span><span>;</span>
<span>const</span> <span>{</span> <span>data</span><span>:</span> <span>profiles</span> <span>}</span> <span>=</span> <span>useQuery</span><span>({</span>
<span>queryKey</span><span>:</span> <span>[</span><span>"</span><span>profiles</span><span>"</span><span>,</span> <span>"</span><span>user</span><span>"</span><span>],</span>
<span>queryFn</span><span>:</span> <span>()</span> <span>=></span> <span>getData</span><span>(</span><span>"</span><span>profiles</span><span>"</span><span>),</span>
<span>});</span>
<span>const</span> <span>{</span> <span>data</span><span>:</span> <span>permissions</span> <span>}</span> <span>=</span> <span>useQuery</span><span>({</span>
<span>queryKey</span><span>:</span> <span>[</span><span>"</span><span>permissions</span><span>"</span><span>,</span> <span>"</span><span>user</span><span>"</span><span>],</span>
<span>queryFn</span><span>:</span> <span>()</span> <span>=></span> <span>getData</span><span>(</span><span>"</span><span>permissions</span><span>"</span><span>),</span>
<span>});</span>
服务器内存消耗高
当您在 React Query 中为每个请求创建 QueryClient 时,它会生成特定于该客户端的隔离缓存。该缓存在内存中保留一段指定的时间(称为 gcTime)。如果在此期间存在大量请求,则可能会导致服务器上出现大量内存消耗。
默认情况下,在服务器上,gcTime 设置为 Infinity,这意味着手动垃圾收集被禁用,并且一旦请求完成,内存就会自动清除。但是,如果您设置非无限 gcTime,则您有责任尽早清除缓存以防止内存使用过多。
避免将 gcTime 设置为 0,因为它可能会导致水合错误。水合边界将必要的数据放入缓存中进行渲染。如果垃圾收集器在渲染完成之前删除此数据,则可能会导致问题。
相反,请考虑将其设置为 2 * 1000,以便应用程序有足够的时间来引用数据。
要管理内存消耗并在不再需要时清除缓存,您可以在处理请求并向客户端发送脱水状态后调用 queryClient.clear()
。或者,您可以选择较小的 gcTime 来更快地自动清除内存。这可确保有效的内存使用并防止服务器上出现与内存相关的问题。
例子:
<span>const</span> <span>queryClient</span> <span>=</span> <span>new</span> <span>QueryClient</span><span>({</span>
<span>defaultOptions</span><span>:</span> <span>{</span>
<span>queries</span><span>:</span> <span>{</span>
<span>gcTime</span><span>:</span> <span>2</span> <span>*</span> <span>2000</span><span>,</span> <span>// this sets the garbage collection time to 2 seconds</span>
<span>},</span>
<span>},</span>
<span>});</span>
总而言之,React Query 简化了获取和缓存数据的过程,尤其是在处理服务器端渲染时。
通过提前在服务器上获取数据并将其无缝传输到客户端,React Query 确保了流畅一致的用户体验。
此外,通过调整内存管理设置等功能,开发人员可以微调性能以满足应用程序的需求。
借助 React Query,开发人员可以专注于构建引人入胜的应用程序,而无需担心复杂的数据管理任务,最终提供更快、响应更灵敏的用户体验。
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek001/post/20240507/React-Query-with-Next.js-App-Router-for-Advanced-Server-Rendering--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com