高级服务器渲染 | 使用 Next.js 应用路由器进行 React 查询
在本指南中,您将学习如何使用 React Query 进行服务器渲染。
什么是服务器渲染?
服务器渲染是指在服务器上生成初始 HTML,以便用户在页面加载时立即看到一些内容。这可以通过两种方式实现:
服务器端渲染 (SSR):每次请求页面时在服务器上生成 HTML。
静态站点生成 (SSG):在构建时预生成 HTML 或使用以前请求的缓存版本。
为什么服务器渲染有用?
使用客户端渲染,该过程如下所示:
- 加载不带内容的标记。
- 加载 JavaScript。
- 使用查询获取数据。
这需要至少三次服务器往返,用户才能看到任何内容。
服务器渲染简化了这个过程:
- 加载带有内容和初始数据的标记。
- 加载 JavaScript。
步骤 1 完成后,用户立即看到内容,步骤 2 完成后页面变为可交互的。初始数据已包含在标记中,因此最初不需要额外提取数据!
这与 React Query 有何关系?
使用 React Query,你可以在服务端生成/渲染标记之前预取数据,然后在客户端使用这些数据,避免再次进行数据获取。
现在来看看如何实现这些步骤……
初始设置
使用 React Query 的第一步始终是创建一个 queryClient 并将应用程序包装在一个 中<QueryClientProvider>
。进行服务器渲染时,务必在应用内部以 React 状态创建 queryClient 实例(实例引用也可以)。这确保了数据不会在不同用户和请求之间共享,同时在每个组件生命周期中仅创建一次 queryClient。
商店.tsx:
"use client";
import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
export default function Store({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
},
},
}),
);
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
布局.tsx:
import Store from "@/provider/store";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Store>{children}</Store>
</body>
</html>
);
}
使用 Hydration API
只需稍加设置,您就可以使用queryClient在预加载阶段预获取查询,并将该 queryClient 的序列化版本传递给应用的渲染部分,并在那里重复使用。这样就避免了前面提到的缺点。
hydration.tsx :
import {
QueryClient,
dehydrate,
HydrationBoundary,
} from "@tanstack/react-query";
import getData from "@/api/getData";
import React from "react";
export default async function Hydration({
children,
}: {
children: React.ReactNode;
}) {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // this sets the cache time to 5 minutes
},
},
});
await Promise.all([
queryClient.prefetchQuery({
queryKey: ["profiles", "user"],
queryFn: () => getData("profiles"),
}),
queryClient.prefetchQuery({
queryKey: ["permissions", "user"],
queryFn: () => getData("permissions"),
}),
]);
return (
<HydrationBoundary state={dehydrate(queryClient)}>
{children}
</HydrationBoundary>
);
}
新的layout.tsx:
import Hydration from "@/provider/hydration";
import Store from "@/provider/store";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Store>
<Hydration>{children}</Hydration>
</Store>
</body>
</html>
);
}
一般来说,这些是额外的步骤:
-
使用创建一个常量 queryClient
new QueryClient(options)
(设置staleTime很重要,否则,React Query 会在数据到达客户端后立即重新获取数据)。 -
用于
await queryClient.prefetchQuery(...)
要预取的每个查询。尽可能
并行await Promise.all(...)
获取查询。 -
可以有一些未预加载的查询。这些查询不会被服务器渲染;相反,它们会在应用程序交互后在客户端加载。这对于仅在用户交互后显示的内容或页面较靠后的内容非常有用,可以避免阻塞更重要的内容。
-
<HydrationBoundary state={dehydrate(queryClient)}>
使用来自框架加载器的 dehydratedState来包装你的树。
一个重要的细节
当使用 React Query 进行服务端渲染时,实际上有三个 queryClient 实例参与该过程:
-
预加载阶段:
在渲染之前,会创建一个 queryClient 来预取数据。
必要的数据会被获取并存储在这个 queryClient 中。 -
服务器渲染阶段:
数据预取完成后,会被脱水(序列化)并发送到服务器渲染进程。
服务器会创建一个新的 queryClient 并注入脱水后的数据。
这确保服务器为客户端生成完整填充的 HTML。 -
客户端渲染阶段:
脱水数据 (Dehydrated data) 被传递到客户端。
客户端会创建另一个 queryClient 并重新加载数据。
这确保客户端以相同的数据启动,保持一致性,并跳过初始数据获取。
这确保所有进程都以相同的数据开始,因此它们可以返回相同的标记。
然后,通过这个useQuery()
钩子,你可以像平常一样使用预取查询,数据将在预加载阶段进行预取。这意味着,当你useQuery()
在组件中使用预取查询时,React Query 会在组件渲染之前自动处理数据的预取。这确保了数据在需要时可用,从而有助于提高应用程序的性能。
"use client";
import getData from "@/api/getData";
import { useQuery } from "@tanstack/react-query";
const { data: profiles } = useQuery({
queryKey: ["profiles", "user"],
queryFn: () => getData("profiles"),
});
const { data: permissions } = useQuery({
queryKey: ["permissions", "user"],
queryFn: () => getData("permissions"),
});
服务器内存消耗高
在 React Query 中,为每个请求创建一个 QueryClient 时,它会生成一个特定于该客户端的独立缓存。此缓存会在内存中保留一段指定的时间,称为gcTime。如果在此期间请求量很大,则可能导致服务器内存消耗严重。
默认情况下,服务器上的gcTime设置为Infinity,这意味着禁用手动垃圾回收,请求完成后会自动清除内存。但是,如果您设置的gcTime 不是 Infinity,则您有责任尽早清除缓存,以防止内存占用过高。
避免将gcTime设置为 0,因为这可能会导致 Hydration 错误。Hydration Boundary 会将渲染所需的数据放入缓存中。如果垃圾回收器在渲染完成之前移除这些数据,可能会导致问题。建议将其设置为 2 * 1000,以便应用有足够的时间引用这些数据。
要管理内存消耗并在不再需要缓存时清除缓存,您可以queryClient.clear()
在处理请求并将脱水状态发送到客户端后调用。或者,您可以选择较小的gcTime来更快地自动清除内存。这可以确保高效的内存使用,并防止服务器出现与内存相关的问题。
例子:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 2 * 2000, // this sets the garbage collection time to 2 seconds
},
},
});
总而言之,React Query 简化了数据获取和缓存的过程,尤其是在处理服务器端渲染时。通过提前在服务器上获取数据并将其无缝传输到客户端,React Query 确保了流畅一致的用户体验。此外,借助调整内存管理设置等功能,开发者可以微调性能以满足其应用程序的需求。借助 React Query,开发者可以专注于构建引人入胜的应用程序,而无需担心复杂的数据管理任务,最终提供更快、响应更灵敏的用户体验。
鏂囩珷鏉ユ簮锛�https://dev.to/rayenmabrouk/advanced-server-rendering-react-query-with-nextjs-app-router-bi7