您可以使用 clientLoader
和 clientAction
函数直接在浏览器中获取和修改数据。
当使用 SPA 模式时,这些函数是处理数据的主要机制。本指南演示了在服务器端渲染 (SSR) 中利用客户端数据的常见用例。
当使用 React Router 搭配 Backend-For-Frontend (BFF) 架构时,您可能希望绕过 React Router 服务器,直接与后端 API 进行通信。这种方法需要适当的身份验证处理,并假设没有 CORS 限制。以下是实现方法:
loader
加载数据clientLoader
加载数据在这种情况下,React Router 在注水(hydration)时不会调用 clientLoader
,而只会在后续导航时调用它。
export async function loader({
request,
}: Route.LoaderArgs) {
const data = await fetchApiFromServer({ request }); // (1)
return data;
}
export async function clientLoader({
request,
}: Route.ClientLoaderArgs) {
const data = await fetchApiFromClient({ request }); // (2)
return data;
}
有时您需要在渲染组件之前结合服务器和浏览器(如 IndexedDB 或浏览器 SDK)的数据。以下是实现此模式的方法:
loader
加载部分数据HydrateFallback
组件,以便在 SSR 期间渲染,因为此时我们还没有完整的数据集clientLoader.hydrate = true
,这将指示 React Router 在初始文档注水时调用 clientLoaderclientLoader
中将服务器数据与客户端数据结合export async function loader({
request,
}: Route.LoaderArgs) {
const partialData = await getPartialDataFromDb({
request,
}); // (1)
return partialData;
}
export async function clientLoader({
request,
serverLoader,
}: Route.ClientLoaderArgs) {
const [serverData, clientData] = await Promise.all([
serverLoader(),
getClientData(request),
]);
return {
...serverData, // (4)
...clientData, // (4)
};
}
clientLoader.hydrate = true as const; // (3)
export function HydrateFallback() {
return <p>Skeleton rendered during SSR</p>; // (2)
}
export default function Component({
// This will always be the combined set of server + client data
loaderData,
}: Route.ComponentProps) {
return <>...</>;
}
您可以在应用程序中混合使用数据加载策略,为每个路由选择仅服务器端加载或仅客户端加载数据。以下是实现这两种方法的方式:
loader
clientLoader
和一个 HydrateFallback
仅依赖服务器 loader 的路由如下所示
export async function loader({
request,
}: Route.LoaderArgs) {
const data = await getServerData(request);
return data;
}
export default function Component({
loaderData, // (1) - server data
}: Route.ComponentProps) {
return <>...</>;
}
仅依赖客户端 loader 的路由如下所示。
export async function clientLoader({
request,
}: Route.ClientLoaderArgs) {
const clientData = await getClientData(request);
return clientData;
}
// Note: you do not have to set this explicitly - it is implied if there is no `loader`
clientLoader.hydrate = true;
// (2)
export function HydrateFallback() {
return <p>Skeleton rendered during SSR</p>;
}
export default function Component({
loaderData, // (2) - client data
}: Route.ComponentProps) {
return <>...</>;
}
您可以实现客户端缓存(使用内存、localStorage 等)来优化服务器请求。以下是一个演示缓存管理的模式:
loader
加载数据clientLoader.hydrate = true
来预热缓存clientLoader
从缓存中加载后续导航clientAction
中使缓存失效请注意,由于我们没有导出一个 HydrateFallback
组件,我们将会 SSR 路由组件,然后在注水(hydration)时运行 clientLoader
。因此,您的 loader
和 clientLoader
在初始加载时返回相同的数据非常重要,以避免注水错误。
export async function loader({
request,
}: Route.LoaderArgs) {
const data = await getDataFromDb({ request }); // (1)
return data;
}
export async function action({
request,
}: Route.ActionArgs) {
await saveDataToDb({ request });
return { ok: true };
}
let isInitialRequest = true;
export async function clientLoader({
request,
serverLoader,
}: Route.ClientLoaderArgs) {
const cacheKey = generateKey(request);
if (isInitialRequest) {
isInitialRequest = false;
const serverData = await serverLoader();
cache.set(cacheKey, serverData); // (2)
return serverData;
}
const cachedData = await cache.get(cacheKey);
if (cachedData) {
return cachedData; // (3)
}
const serverData = await serverLoader();
cache.set(cacheKey, serverData);
return serverData;
}
clientLoader.hydrate = true; // (2)
export async function clientAction({
request,
serverAction,
}: Route.ClientActionArgs) {
const cacheKey = generateKey(request);
cache.delete(cacheKey); // (4)
const serverData = await serverAction();
return serverData;
}