您可以使用 clientLoader
和 clientAction
函数直接在浏览器中获取和修改数据。
当使用 SPA 模式 时,这些函数是处理数据的主要机制。本指南演示了在服务器端渲染 (SSR) 中利用客户端数据的常见用例。
当将 React Router 与后端即前端 (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 在初始文档 hydration 期间调用 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
在初始加载时返回相同的数据,以避免 hydration 错误。
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;
}