renderToString

将 React 树渲染为 HTML 字符串 - 传统 SSR 方法

核心概述

renderToString 是 React 最早的服务端渲染 API, 用于将 React 树渲染为 HTML 字符串。它在 React 18 之前是 SSR 的标准方法, 但现在已被流式渲染 API (renderToPipeableStream 和 renderToReadableStream) 取代。

⚠️ 不推荐使用:renderToString 不支持流式传输, 必须等待整个应用渲染完成后才发送 HTML。这导致较差的用户体验和性能。 新项目应该使用 renderToPipeableStreamrenderToReadableStream

⚠️ 已弃用的 API

  • 不支持流式传输:必须等待整个应用渲染完成
  • 较差的 TTFB:首字节时间更长
  • 无 Suspense 支持:无法充分利用 Suspense 的流式特性
  • 仅用于兼容:维护旧项目时使用,新项目避免使用

唯一的使用场景:维护无法升级到 React 18 的旧项目, 或在不支持流式传输的环境中运行。

技术规格

导入方式

TypeScript
import { renderToString } from 'react-dom/server';

函数签名

TypeScript
function renderToString(element: ReactNode): string

参数说明

参数类型说明
elementReactNode要渲染的 React 元素

返回值

返回一个 HTML 字符串,包含渲染后的 React 树。

实战演练

1. 基础用法

最简单的 renderToString 用法:

TypeScript
import express from 'express';
import { renderToString } from 'react-dom/server';
import App from './App';

const app = express();

app.get('/', (req, res) => {
  const html = renderToString(<App />);

  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script src="/client.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000);

2. 结合数据获取 (传统方法)

在渲染前获取所有数据:

TypeScript
import express from 'express';
import { renderToString } from 'react-dom/server';
import App from './App';

const app = express();

app.get('/', async (req, res) => {
  // ❌ 必须等待所有数据完成才能开始渲染
  const [user, posts] = await Promise.all([
    fetchUser(req.session.userId),
    fetchPosts(),
  ]);

  const html = renderToString(<App user={user} posts={posts} />);

  res.send(`
    <!DOCTYPE html>
    <html>
      <head><title>My App</title></head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__INITIAL_DATA__ = ${JSON.stringify({ user, posts })};
        </script>
        <script src="/client.js"></script>
      </body>
    </html>
  `);
});

迁移指南

如果你的项目正在使用 renderToString,强烈建议升级到流式渲染 API:

迁移到 renderToPipeableStream (Node.js)

TypeScript
// ❌ 旧代码:renderToString
import { renderToString } from 'react-dom/server';

app.get('/', (req, res) => {
  const html = renderToString(<App />);
  res.send(`<!DOCTYPE html><html><body><div id="root">${html}</div>...</body></html>`);
});

// ✅ 新代码:renderToPipeableStream
import { renderToPipeableStream } from 'react-dom/server';

app.get('/', (req, res) => {
  const stream = renderToPipeableStream(<App />, {
    onShellReady() {
      res.statusCode = 200;
      res.setHeader('Content-type', 'text/html');
      res.write('<!DOCTYPE html><html><body><div id="root">');
      stream.pipe(res);
    },
  });
});

迁移到 renderToReadableStream (Edge Runtime)

TypeScript
// ❌ 旧代码:renderToString
import { renderToString } from 'react-dom/server';

const html = renderToString(<App />);
return new Response(html, {
  headers: { 'Content-Type': 'text/html' },
});

// ✅ 新代码:renderToReadableStream
import { renderToReadableStream } from 'react-dom/server';

const stream = await renderToReadableStream(<App />);
return new Response(stream, {
  headers: { 'Content-Type': 'text/html' },
});

性能对比

指标renderToString流式渲染 API
TTFB较慢 (等待完整渲染)更快 (立即发送 shell)
LCP较慢更快
Suspense 支持
流式传输

延伸阅读

这篇文章有帮助吗?