您可以使用 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;
}