Remix 在 v2.4.0
中引入了对“客户端数据”(RFC)的支持,允许您通过路由的 clientLoader
/clientAction
导出选择在浏览器中运行路由加载器/操作。
这些新的导出有点像一把锋利的刀,不建议作为您主要的数据加载/提交机制 - 而是为您提供了一种杠杆,可以用来处理以下一些高级用例
请谨慎使用这些新的导出!如果您不小心 - 很容易让您的 UI 不同步。默认情况下,Remix 非常努力地确保这种情况不会发生 - 但是一旦您控制了您自己的客户端缓存,并可能阻止 Remix 执行其正常的服务器 fetch
调用 - 那么 Remix 就无法再保证您的 UI 保持同步。
在 BFF 架构中使用 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
在初始加载时必须返回相同的数据,以避免hydration错误。
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
迁移,以开始将数据加载迁移到服务器