resume 将预渲染的 React 树流式传输到一个 可读取的 Web 流。

const stream = await resume(reactNode, postponedState, options?)

注意

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

🌐 This API depends on Web Streams. For Node.js, use resumeToNodeStream instead.


参考

🌐 Reference

resume(node, postponedState, options?)

调用 resume 将预渲染的 React 树作为 HTML 渲染到 可读 Web 流

🌐 Call resume to resume rendering a pre-rendered React tree as HTML into a Readable Web Stream.

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:你调用的 React 节点 prerender。例如,一个像 <App /> 这样的 JSX 元素。它预计表示整个文档,因此 App 组件应渲染 <html> 标签。
  • postponedState:从 prerender API 返回的不可见 postpone 对象,从你存储它的地方加载(例如 redis、文件或 S3)。
  • 可选 options:一个带有流选项的对象。

返回

🌐 Returns

resume 返回一个 Promise:

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

🌐 The returned stream has an additional property:

  • allReady:一个在所有渲染完成时解决的 Promise。你可以在返回响应之前 await stream.allReady 用于爬虫和静态生成。如果你这样做,你将无法获得任何渐进式加载。流将包含最终的 HTML。

注意事项

🌐 Caveats

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

用法

🌐 Usage

恢复预渲染

🌐 Resuming a prerender

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>Me and my descendants can be prerendered</header>;
}

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

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

  useEffect(() => {
    console.log("reached interactivity!");
  }, []);

  return (
    <main>
      Hello, {user.name}!
      <button onClick={() => console.log("hydrated!")}>
        Clicking me requires hydration.
      </button>
    </main>
  );
}

function Shell({ children }) {
  // In a real app, this is where you would put your html and body.
  // We're just using tags here we can include in an existing body for demonstration purposes
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

function App() {
  return (
    <Shell>
      <Suspense fallback="loading header">
        <Header />
      </Suspense>
      <Suspense fallback="loading main">
        <Main />
      </Suspense>
    </Shell>
  );
}

async function main(frame) {
  // Layer 1
  const controller = new AbortController();
  const prerenderedApp = prerender(<App />, {
    signal: controller.signal,
    onError(error) {
      if (error instanceof Postponed) {
      } else {
        console.error(error);
      }
    },
  });
  // We're immediately aborting in a macrotask.
  // Any data fetching that's not available synchronously, or in a microtask, will not have finished.
  setTimeout(() => {
    controller.abort(new Postponed());
  });

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

  // Layer 2
  // Just waiting here for demonstration purposes.
  // In a real app, the prelude and postponed state would've been serialized in Layer 1 and Layer would deserialize them.
  // The prelude content could be flushed immediated as plain HTML while
  // React is continuing to render from where the prerender left off.
  await sleep(2000);

  // You would get the cookies from the incoming HTTP request
  resolveCookies({ sessionID: "abc" });

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

  await flushReadableStreamToFrame(stream, frame);

  // Layer 3
  // Just waiting here for demonstration purposes.
  await sleep(2000);

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

main(document.getElementById("container"));

延伸阅读

🌐 Further reading

Resuming 的行为类似于 renderToReadableStream。要查看更多示例,请查看 renderToReadableStream 的使用部分prerender 的使用部分 包含了如何具体使用 prerender 的示例。

🌐 Resuming behaves like renderToReadableStream. For more examples, check out the usage section of renderToReadableStream. The usage section of prerender includes examples of how to use prerender specifically.