您可以使用 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 在初始文档水合过程中调用 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
一个仅依赖于服务器加载器的路由如下所示
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 <>...</>;
}
一个仅依赖于客户端加载器的路由如下所示。
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,然后在水合时运行 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;
}