Streaming
このコンテンツはまだ日本語訳がありません。
流媒体
流式传输允许您在内容可用时立即提供内容,而不是等待整个页面内容准备就绪,从而增强用户体验。
确保您的托管服务提供商支持流媒体,并非所有提供商都支持。如果您的响应似乎无法流式传输,这可能是原因。
步骤
流式传输数据分为三个步骤:
- 项目设置:我们需要确保我们的客户端和服务器入口点已设置为支持流式传输
- 组件设置:我们需要确保我们的组件可以呈现流式传输数据
- 延迟加载器数据:最后我们可以在加载器中延迟数据
1. 项目设置
从一开始就准备就绪:使用入门模板创建的 Remix 应用程序已预先配置为流式传输。
是否需要手动设置?:如果您的项目是从头开始的,或者使用了较旧的模板,请验证 entry.server.tsx
和 entry.client.tsx
是否支持流式传输。如果您没有看到这些文件,则表示您正在使用默认设置,并且支持流式传输。如果您创建了自己的条目,以下是模板默认设置供您参考:
- entry.client.tsx
- entry.server.tsx:
- cloudflare
- deno
- node
2. 组件设置
没有流式传输的路由模块可能看起来像这样:
import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/denoimport { json } from "@remix-run/node"; // or cloudflare/denoimport { 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/denoimport { json } from "@remix-run/node"; // or cloudflare/denoimport { 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/denoimport { defer } from "@remix-run/node"; // or cloudflare/denoimport { 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
方法。
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
,如下所示:
const { pipe, abort } = renderToPipeableStream( <RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />, { nonce: "secretnoncevalue", /* ...remaining fields */ });
这将确保 nonce 值包含在任何延迟脚本标签中。