Remix 在 v2.4.0
中引入了对“客户端数据”的支持(RFC),允许您通过路由中的 clientLoader
/clientAction
导出选择在浏览器中运行路由加载器/操作。
这些新的导出有点像一把锋利的刀,不建议作为您的主要数据加载/提交机制,而是为您提供一些以下高级用例的杠杆:
请谨慎使用这些新的导出!如果您不小心,很容易使您的 UI 不同步。Remix 开箱即用会非常努力地确保这种情况不会发生 - 但是一旦您控制了自己的客户端缓存,并可能阻止 Remix 执行其正常的服务器 fetch
调用 - 那么 Remix 就无法再保证您的 UI 保持同步。
当在 BFF 架构中使用 Remix 时,跳过 Remix 服务器环节并直接访问您的后端 API 可能是有利的。这假设您能够相应地处理身份验证,并且不受 CORS 问题的限制。您可以按如下方式跳过 Remix BFF 环节:
loader
加载数据clientLoader
加载数据在这种情况下,Remix 不会在水合时调用 clientLoader
,并且仅在后续导航时调用。
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import type { ClientLoaderFunctionArgs } from "@remix-run/react";
export async function loader({
request,
}: LoaderFunctionArgs) {
const data = await fetchApiFromServer({ request }); // (1)
return json(data);
}
export async function clientLoader({
request,
}: ClientLoaderFunctionArgs) {
const data = await fetchApiFromClient({ request }); // (2)
return data;
}
有时,您可能想利用“全栈状态”,其中一部分数据来自服务器,一部分数据来自浏览器(即 IndexedDB
或其他浏览器 SDK)- 但是在您拥有完整的数据集之前,您无法渲染组件。您可以按如下方式组合这两个数据源:
loader
加载部分数据HydrateFallback
组件以在 SSR 期间渲染,因为我们还没有完整的数据集clientLoader.hydrate = true
,这指示 Remix 在初始文档水合期间调用 clientLoaderclientLoader
中将服务器数据与客户端数据组合import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import type { ClientLoaderFunctionArgs } from "@remix-run/react";
export async function loader({
request,
}: LoaderFunctionArgs) {
const partialData = await getPartialDataFromDb({
request,
}); // (1)
return json(partialData);
}
export async function clientLoader({
request,
serverLoader,
}: ClientLoaderFunctionArgs) {
const [serverData, clientData] = await Promise.all([
serverLoader(),
getClientData(request),
]);
return {
...serverData, // (4)
...clientData, // (4)
};
}
clientLoader.hydrate = true; // (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
const data = useLoaderData();
return <>...</>;
}
您可能希望在您的应用程序中混合和匹配数据加载策略,以便某些路由仅在服务器上加载数据,而某些路由仅在客户端上加载数据。您可以按路由选择如下:
loader
clientLoader
和 HydrateFallback
仅依赖于服务器加载器的路由如下所示:
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
export async function loader({
request,
}: LoaderFunctionArgs) {
const data = await getServerData(request);
return json(data);
}
export default function Component() {
const data = useLoaderData(); // (1) - server data
return <>...</>;
}
仅依赖于客户端加载器的路由如下所示。
import type { ClientLoaderFunctionArgs } from "@remix-run/react";
export async function clientLoader({
request,
}: ClientLoaderFunctionArgs) {
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() {
const data = useLoaderData(); // (2) - client data
return <>...</>;
}
您可以利用客户端缓存(内存、本地存储等)来绕过某些服务器调用,如下所示:
loader
加载数据clientLoader.hydrate = true
以预先填充缓存clientLoader
从缓存加载后续导航。clientAction
中使缓存失效。请注意,由于我们没有导出 HydrateFallback
组件,我们将对路由组件进行 SSR,然后在水合 (hydration) 时运行 clientLoader
。因此,重要的是你的 loader
和 clientLoader
在初始加载时返回相同的数据,以避免水合错误。
import type {
ActionFunctionArgs,
LoaderFunctionArgs,
} from "@remix-run/node";
import { json } from "@remix-run/node";
import type {
ClientActionFunctionArgs,
ClientLoaderFunctionArgs,
} from "@remix-run/react";
export async function loader({
request,
}: LoaderFunctionArgs) {
const data = await getDataFromDb({ request }); // (1)
return json(data);
}
export async function action({
request,
}: ActionFunctionArgs) {
await saveDataToDb({ request });
return json({ ok: true });
}
let isInitialRequest = true;
export async function clientLoader({
request,
serverLoader,
}: ClientLoaderFunctionArgs) {
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,
}: ClientActionFunctionArgs) {
const cacheKey = generateKey(request);
cache.delete(cacheKey); // (4)
const serverData = await serverAction();
return serverData;
}
我们计划在 SPA 模式 发布后编写单独的迁移指南,但目前我们预计流程将如下:
createBrowserRouter
/RouterProvider
在你的 React Router SPA 中引入数据模式。loader
函数都充当 clientLoader
。loader
函数为 clientLoader
。clientLoader
进行。clientLoader -> loader
迁移,以便开始将数据加载移到服务器。