コンテンツにスキップ

Streaming

このコンテンツはまだ日本語訳がありません。

流媒体

流式传输允许您在内容可用时立即提供内容,而不是等待整个页面内容准备就绪,从而增强用户体验。

确保您的托管服务提供商支持流媒体,并非所有提供商都支持。如果您的响应似乎无法流式传输,这可能是原因。

步骤

流式传输数据分为三个步骤:

  1. 项目设置:我们需要确保我们的客户端和服务器入口点已设置为支持流式传输
  2. 组件设置:我们需要确保我们的组件可以呈现流式传输数据
  3. 延迟加载器数据:最后我们可以在加载器中延迟数据

1. 项目设置

从一开始就准备就绪:使用入门模板创建的 Remix 应用程序已预先配置为流式传输。

是否需要手动设置?:如果您的项目是从头开始的,或者使用了较旧的模板,请验证 entry.server.tsxentry.client.tsx 是否支持流式传输。如果您没有看到这些文件,则表示您正在使用默认设置,并且支持流式传输。如果您创建了自己的条目,以下是模板默认设置供您参考:

2. 组件设置

没有流式传输的路由模块可能看起来像这样:

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>
</>
);
}

在我们开始延迟数据之前,此代码将继续工作。最好先编写组件代码。如果遇到问题,可以更轻松地找到问题所在。

3. 在 Loader 中延迟数据

现在我们的项目和路由组件已设置好流数据,我们可以开始在加载器中延迟数据。我们将使用 Remix 中的 defer 实用程序来执行此操作。

注意异步承诺代码的变化。

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
}

我们不再等待评论承诺,而是将其传递给defer。这会告诉 Remix 通过网络将该承诺传输到浏览器。

就这样!您现在应该正在将数据传输到浏览器。

避免低效的流式传输

在等待任何其他承诺之前,发起延迟数据的承诺非常重要,否则您将无法充分利用流式传输。请注意与这个效率较低的代码示例的区别:

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中止 ReactrenderToPipeableStream方法。

entry.server.tsx
const ABORT_DELAY = 5_000;
// ...
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>
// ...
);
// ...
setTimeout(abort, ABORT_DELAY);

使用内容安全策略进行流式传输

流式传输的工作原理是,在延迟承诺解析时将脚本标记插入 DOM。如果您的页面包含 脚本的内容安全策略,则您需要通过在 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" />
  • entry.server.ts 中调用 renderToPipeableStream,如下所示:
entry.server.tsx
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
nonce: "secretnoncevalue",
/* ...remaining fields */
}
);

这将确保 nonce 值包含在任何延迟脚本标签中。