resume 会将一个预渲染的 React 树流式传输到一个 Readable Web Stream.
const stream = await resume(reactNode, postponedState, options?)Reference
resume(node, postponedState, options?)
调用 resume 将一个预渲染的 React 树以 HTML 的形式恢复渲染到一个 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:你在调用prerender时使用的 React 节点。例如,一个像<App />这样的 JSX 元素。它应当表示整个文档,因此App组件应该渲染<html>标签。postponedState:从 prerender API 返回的、不透明的postpone对象,它是从你存储它的任何位置加载的(例如 redis、文件或 S3)。- optional
options:包含流式传输选项的对象。- optional
nonce:一个nonce字符串,用于允许脚本满足script-srcContent-Security-Policy。 - optional
signal:一个 abort signal,可让你 中止服务器渲染,并在客户端渲染剩余内容。 - optional
onError:每当发生服务器错误时触发的回调,无论该错误是否 可恢复 或 不可恢复。 默认情况下,它只会调用console.error。如果你将其覆盖为 记录崩溃报告, 请确保你仍然调用console.error。
- optional
Returns
resume 返回一个 Promise:
- 如果
resume成功生成了一个 shell,该 Promise 将解析为一个 Readable Web Stream.,它可以被管道传输到一个 Writable Web Stream.。 - 如果 shell 中发生错误,Promise 将以该错误拒绝。
返回的流还有一个额外属性:
allReady:一个 Promise,当所有渲染完成时会解析。你可以在返回响应之前await stream.allReady用于爬虫和静态生成。 如果这样做,你将不会获得任何渐进式加载。流中将包含最终的 HTML。
Caveats
resume不接受bootstrapScripts、bootstrapScriptContent或bootstrapModules的选项。相反,你需要将这些选项传递给生成postponedState的prerender调用。你也可以手动将 bootstrap 内容注入到 writable stream 中。resume不接受identifierPrefix,因为该前缀在prerender和resume中都需要保持一致。- 由于
nonce不能提供给 prerender,因此只有在你没有向 prerender 提供脚本时,才应将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>我和我的后代都可以被预渲染</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("已完成 hydration!")}> 点击我需要 hydration。 </button> </main> ); } function Shell({ children }) { // 在真实应用中,这里应该放置你的 html 和 body。 // 这里只是为了演示,在现有的 body 中包含这些标签 return ( <html> <body>{children}</body> </html> ); } function App() { return ( <Shell> <Suspense fallback="正在加载 header"> <Header /> </Suspense> <Suspense fallback="正在加载 main"> <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 state 本应在第 1 层被序列化,随后由第 2 层反序列化它们。 // 当 React 继续从 prerender 中断的位置渲染时,prelude 内容可以作为普通 HTML 立即刷新输出。 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"));
进一步阅读
resuming 的行为类似于 renderToReadableStream。更多示例请查看 renderToReadableStream 的使用部分。
prerender 的使用部分 包含了如何专门使用 prerender 的示例。