流式传输允许您通过在内容可用时立即提供内容来增强用户体验,而不是等待页面全部内容准备就绪。
确保您的托管提供商支持流式传输,并非所有托管提供商都支持。 如果您的响应似乎没有流式传输,这可能是原因。
流式传输数据有三个步骤
**从一开始就准备就绪:**使用入门模板创建的 Remix 应用程序预先配置了流式传输。
**需要手动设置?** 如果您的项目从头开始或使用旧模板,请验证 entry.server.tsx
和 entry.client.tsx
是否具有流式传输支持。 如果您没有看到这些文件,那么您正在使用默认值,并且支持流式传输。 如果您创建了自己的入口点,以下列出了供您参考的模板默认值
没有流式传输的路由模块可能如下所示
import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useLoaderData } from "@remix-run/react";
export async function loader({
params,
}: LoaderFunctionArgs) {
const [product, reviews] = await Promise.all([
db.getProduct(params.productId),
db.getReviews(params.productId),
]);
return json({ product, reviews });
}
export default function Product() {
const { product, reviews } =
useLoaderData<typeof loader>();
return (
<>
<ProductPage data={product} />
<ProductReviews data={reviews} />
</>
);
}
为了渲染流式传输数据,您需要使用来自 React 的 <Suspense>
和来自 Remix 的 <Await>
。 这有点模板,但很简单
import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { Await, useLoaderData } from "@remix-run/react";
import { Suspense } from "react";
import { ReviewsSkeleton } from "./reviews-skeleton";
export async function loader({
params,
}: LoaderFunctionArgs) {
// existing code
}
export default function Product() {
const { product, reviews } =
useLoaderData<typeof loader>();
return (
<>
<ProductPage data={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<Await resolve={reviews}>
{(reviews) => <ProductReviews data={reviews} />}
</Await>
</Suspense>
</>
);
}
即使在我们开始延迟数据之前,此代码仍然有效。 最好先完成组件代码。 如果遇到问题,更容易找出问题所在。
现在我们的项目和路由组件已设置为流式传输数据,我们可以开始在加载器中延迟数据。 我们将使用来自 Remix 的 defer
实用程序来完成此操作。
请注意异步 promise 代码的更改。
import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { defer } from "@remix-run/node"; // or cloudflare/deno
import { Await, useLoaderData } from "@remix-run/react";
import { Suspense } from "react";
import { ReviewsSkeleton } from "./reviews-skeleton";
export async function loader({
params,
}: LoaderFunctionArgs) {
// 👇 note this promise is not awaited
const reviewsPromise = db.getReviews(params.productId);
// 👇 but this one is
const product = await db.getProduct(params.productId);
return defer({
product,
reviews: reviewsPromise,
});
}
export default function Product() {
const { product, reviews } =
useLoaderData<typeof loader>();
// existing code
}
我们没有等待 reviews promise,而是将其传递给 defer
。 这告诉 Remix 通过网络将该 promise 流式传输到浏览器。
就是这样! 您现在应该将数据流式传输到浏览器。
重要的是在您等待任何其他 promise 之前启动延迟数据的 promise,否则您将无法获得流式传输的全部好处。 请注意与这个效率较低的代码示例的区别
export async function loader({
params,
}: LoaderFunctionArgs) {
const product = await db.getProduct(params.productId);
// 👇 this won't initiate loading until `product` is done
const reviewsPromise = db.getReviews(params.productId);
return defer({
product,
reviews: reviewsPromise,
});
}
在使用 defer
进行流式传输时,您可以通过在 entry.server.tsx
文件中的 <RemixServer abortDelay>
属性(默认值为 5 秒)来告诉 Remix 等待延迟数据解析的时间,然后再超时。 如果您目前没有 entry.server.tsx
文件,您可以通过 npx remix reveal entry.server
来公开它。 您还可以使用此值通过 setTimeout
中止 React renderToPipeableStream
方法。
const ABORT_DELAY = 5_000;
// ...
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>
// ...
);
// ...
setTimeout(abort, ABORT_DELAY);
流式传输通过将脚本标签插入 DOM 作为延迟的 promise 解析来工作。 如果您的页面包含一个 脚本的内容安全策略,您将需要要么通过在您的 Content-Security-Policy
标头中包含 script-src 'self' 'unsafe-inline'
来弱化您的安全策略,要么为所有脚本标签添加 nonce。
如果您使用 nonce,则需要在三个地方包含它
Content-Security-Policy
标头,如下所示:Content-Security-Policy: script-src 'nonce-secretnoncevalue'
<Scripts />
、<ScrollRestoration />
和 <LiveReload />
组件,如下所示:<Scripts nonce="secretnoncevalue" />
renderToPipeableStream
的 entry.server.ts
中,如下所示const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
nonce: "secretnoncevalue",
/* ...remaining fields */
}
);
这将确保 nonce 值包含在任何延迟的脚本标签中。