知海

resume()

ReactAPI 参考:React DOM

resume 用于以可读Web流的形式流式输出预渲染的 React 树。

js 复制代码
const stream = await resume(reactNode, postponedState, options?)

此 API 依赖于 Web Streams。对于 Node.js,请改用 resumeToNodeStream


参考 {/reference/}

resume(node, postponedState, options?) {/resume/}

调用 resume 将以 可读Web流 的形式恢复渲染预渲染的 React 树为 HTML。

js 复制代码
import { resume } from 'react-dom/server';
import {getPostponedState} from './storage';

async function handler(request, writable) {
  const postponed = await getPostponedState(request);
  const resumeStream = await resume(<App />, postponed);
  return resumeStream.pipeTo(writable)
}

更多示例见下文。

参数 {/parameters/}

  • reactNode:你传递给 prerender 的 React 节点。例如,一个 JSX 元素 <App />。它期望表示整个文档,因此 App 组件应渲染 <html> 标签。
  • postponedState:从 prerender API 返回的不透明 postpone 对象,从你存储它的任何位置(例如 redis、文件或 S3)加载。
  • 可选 options:包含流式选项的对象。

返回值 {/returns/}

resume 返回一个 Promise:

  • 如果 resume 成功生成了一个 外壳,该 Promise 将解析为一个 可读Web流,可以管道传输到一个 可写Web流
  • 如果外壳中发生错误,该 Promise 将以此错误拒绝。

返回的流有一个额外的属性:

注意事项 {/caveats/}

  • resume 不接受 bootstrapScriptsbootstrapScriptContentbootstrapModules 选项。相反,你需要将这些选项传递给生成 postponedStateprerender 调用。你也可以手动将引导内容注入可写流。
  • resume 不接受 identifierPrefix,因为前缀在 prerenderresume 中必须相同。
  • 由于无法向 prerender 提供 nonce,如果你未向 prerender 提供脚本,则只应向 resume 提供 nonce
  • resume 会从根节点开始重新渲染,直到找到一个未完全预渲染的组件。只有完全预渲染的组件(该组件及其子组件完成预渲染)才会被完全跳过。

用法 {/usage/}

恢复预渲染 {/resuming-a-prerender/}

js src/App.js hidden 复制代码
html public/index.html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <iframe id="container"></iframe>
</body>
</html>
js src/index.js 复制代码
import {
  flushReadableStreamToFrame,
  getUser,
  Postponed,
  sleep,
} from "./demo-helpers";
import { StrictMode, Suspense, use, useEffect } from "react";
import { prerender } from "react-dom/static";
import { resume } from "react-dom/server";
import { hydrateRoot } from "react-dom/client";

function Header() {
  return <header>我和我的后代组件可以被预渲染</header>;
}

const { promise: cookies, resolve: resolveCookies } = Promise.withResolvers();

function Main() {
  const { sessionID } = use(cookies);
  const user = getUser(sessionID);

  useEffect(() => {
    console.log("已到达交互阶段!");
  }, []);

  return (
    <main>
      你好,{user.name}!
      <button onClick={() => console.log("已水合!")}>
        点击我需要水合作用。
      </button>
    </main>
  );
}

function Shell({ children }) {
  // 在实际应用中,这里是放置 html 和 body 的位置。
  // 这里我们仅使用可以包含在现有 body 中的标签用于演示目的
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

function App() {
  return (
    <Shell>
      <Suspense fallback="加载头部中">
        <Header />
      </Suspense>
      <Suspense fallback="加载主体中">
        <Main />
      </Suspense>
    </Shell>
  );
}

async function main(frame) {
  // 第 1 层
  const controller = new AbortController();
  const prerenderedApp = prerender(<App />, {
    signal: controller.signal,
    onError(error) {
      if (error instanceof Postponed) {
      } else {
        console.error(error);
      }
    },
  });
  // 我们立即在一个宏任务中中止。
  // 任何无法在同步或微任务中获取的数据都不会完成。
  setTimeout(() => {
    controller.abort(new Postponed());
  });

  const { prelude, postponed } = await prerenderedApp;
  await flushReadableStreamToFrame(prelude, frame);

  // 第 2 层
  // 这里只是为了演示目的而等待。
  // 在实际应用中,prelude 和 postponed 状态会在第 1 层被序列化,第 2 层会将其反序列化。
  // prelude 内容可以立即作为纯 HTML 刷新,而
  // React 则从预渲染中断的地方继续渲染。
  await sleep(2000);

  // 你将从来访的 HTTP 请求中获取 cookies
  resolveCookies({ sessionID: "abc" });

  const stream = await resume(<App />, postponed);

  await flushReadableStreamToFrame(stream, frame);

  // 第 3 层
  // 这里只是为了演示目的而等待。
  await sleep(2000);

  hydrateRoot(frame.contentWindow.document, <App />);
}

main(document.getElementById("container"));
js src/demo-helpers.js 复制代码
export async function flushReadableStreamToFrame(readable, frame) {
  const document = frame.contentWindow.document;
  const decoder = new TextDecoder();
  for await (const chunk of readable) {
    const partialHTML = decoder.decode(chunk);
    document.write(partialHTML);
  }
}

// 这不必是一个错误。
// 你也可以使用任何其他方式来判断预渲染期间的错误是
// 来自有意的中止还是真实的错误。
export class Postponed extends Error {}

// 这里我们只是硬编码一个会话来演示。
export function getUser(sessionID) {
  return {
    name: "Alice",
  };
}

export function sleep(timeoutMS) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeoutMS);
  });
}

延伸阅读 {/further-reading/}

恢复渲染的行为类似于 renderToReadableStream。更多示例,请查看 renderToReadableStream 的用法部分
prerender 的用法部分 包含了如何专门使用 prerender 的示例。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。