renderToStaticMarkup
将 React 树渲染为静态 HTML - 类似于 renderToString,但不添加额外的 React 内部属性
核心概述
renderToStaticMarkup 与 renderToString 类似, 但有一个关键区别:它不会创建额外的 DOM 属性 (如 data-reactroot), 生成的 HTML 更干净、更轻量。这使它非常适合用于纯静态页面, 不需要客户端 hydration 的场景。
核心特点:
- 不添加 React 内部属性 (
data-reactroot等) - 生成的 HTML 更小、更干净
- 不支持客户端 hydration (因为没有内部属性)
- 适用于纯静态内容生成
适用场景:
- 生成电子邮件 HTML (邮件客户端不支持 JavaScript)
- 生成 RSS/Atom feeds
- 静态网站生成器 (SSG) 的非交互部分
- 生成可嵌入其他网站的 HTML 片段
- 预览/打印版本的内容
📧 经典用例:电子邮件
电子邮件客户端不支持 JavaScript,因此不需要 hydration。 使用 renderToStaticMarkup 生成的 HTML 更小,兼容性更好。
JavaScript
// 生成电子邮件 HTML
const emailHTML = renderToStaticMarkup(<EmailTemplate {...data} />);技术规格
导入方式
TypeScript
import { renderToStaticMarkup } from 'react-dom/server';函数签名
TypeScript
function renderToStaticMarkup(element: ReactNode): string参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
element | ReactNode | 要渲染的 React 元素 |
返回值
返回一个 HTML 字符串,不包含 React 内部属性。
实战演练
1. 生成电子邮件 HTML
最常见的使用场景 - 生成电子邮件内容:
TypeScript
import { renderToStaticMarkup } from 'react-dom/server';
import EmailTemplate from './EmailTemplate';
interface EmailData {
to: string;
subject: string;
userName: string;
verificationUrl: string;
}
async function sendVerificationEmail(data: EmailData) {
// 渲染电子邮件模板
const emailHTML = renderToStaticMarkup(
<EmailTemplate
userName={data.userName}
verificationUrl={data.verificationUrl}
/>
);
// 发送电子邮件
await emailService.send({
to: data.to,
subject: data.subject,
html: emailHTML, // 纯 HTML,无 JavaScript
});
}
// EmailTemplate.jsx
function EmailTemplate({ userName, verificationUrl }) {
return (
<div style={{ fontFamily: 'Arial, sans-serif' }}>
<h1>欢迎, {userName}!</h1>
<p>请点击下面的链接验证您的邮箱:</p>
<a href={verificationUrl}>验证邮箱</a>
<p>如果您没有注册,请忽略此邮件。</p>
</div>
);
}2. 生成 RSS Feed
为博客生成 RSS/Atom feeds:
TypeScript
import { renderToStaticMarkup } from 'react-dom/server';
function RSSFeed({ posts }) {
return (
<rss version="2.0">
<channel>
<title>My Blog</title>
<link>https://example.com</link>
<description>My awesome blog</description>
{posts.map(post => (
<item key={post.id}>
<title>{post.title}</title>
<link>{`https://example.com/posts/${post.slug}`}</link>
<description>{post.excerpt}</description>
<pubDate>{post.publishedAt.toUTCString()}</pubDate>
</item>
))}
</channel>
</rss>
);
}
// API 路由
app.get('/rss', async (req, res) => {
const posts = await fetchPosts();
const rss = renderToStaticMarkup(<RSSFeed posts={posts} />);
res.set('Content-Type', 'application/rss+xml');
res.send(rss);
});3. 生成可嵌入的 HTML 片段
生成可嵌入其他网站的 HTML widget:
TypeScript
import { renderToStaticMarkup } from 'react-dom/server';
function EmbedWidget({ productId }) {
return (
<div className="product-widget">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.price}</p>
<a href={product.url}>查看详情</a>
</div>
);
}
// 生成嵌入代码
app.get('/embed/:productId', async (req, res) => {
const product = await fetchProduct(req.params.productId);
const widgetHTML = renderToStaticMarkup(<EmbedWidget product={product} />);
// 用户可以复制这段 HTML 嵌入到他们的网站
const embedCode = `
<script src="https://your-domain.com/widget.js"></script>
<div id="widget-root">${widgetHTML}</div>
`;
res.json({ embedCode });
});4. 静态网站生成
在构建时生成静态 HTML 页面:
TypeScript
import { renderToStaticMarkup } from 'react-dom/server';
import fs from 'fs';
import path from 'path';
async function buildStaticPages() {
const posts = await fetchAllPosts();
for (const post of posts) {
// 生成静态 HTML
const html = renderToStaticMarkup(
<html>
<head>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
</head>
<body>
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
</body>
</html>
);
// 写入文件
const filePath = path.join('dist', `${post.slug}.html`);
fs.writeFileSync(filePath, `<!DOCTYPE html>${html}`);
}
}
// 运行构建
buildStaticPages();renderToString vs renderToStaticMarkup
选择合适的 API 取决于你的使用场景:
| 特性 | renderToString | renderToStaticMarkup |
|---|---|---|
| React 内部属性 | ✅ 添加 | ❌ 不添加 |
| 支持 Hydration | ✅ | ❌ |
| HTML 大小 | 较大 (有额外属性) | 较小 (干净 HTML) |
| 使用场景 | 需要 hydration 的 SSR (已被流式 API 取代) | 静态 HTML,电子邮件,RSS feeds |
输出对比
HTML
// renderToString 输出
<div data-reactroot="">
<h1>Hello World</h1>
</div>
// renderToStaticMarkup 输出 (更干净)
<div>
<h1>Hello World</h1>
</div>避坑指南
陷阱 1: 试图进行 Hydration
问题:renderToStaticMarkup 生成的 HTML 无法进行 hydration。
TypeScript
// ❌ 错误:试图对 renderToStaticMarkup 的输出进行 hydration
const html = renderToStaticMarkup(<App />);
document.getElementById('root').innerHTML = html;
hydrateRoot(document.getElementById('root'), <App />); // 无法匹配!
// ✅ 正确:如果需要 hydration,使用 renderToString 或流式 API
const html = renderToString(<App />);
document.getElementById('root').innerHTML = html;
hydrateRoot(document.getElementById('root'), <App />);陷阱 2: 在交互式页面中使用
问题:用于需要 JavaScript 交互的页面。
TypeScript
// ❌ 错误:用于交互式应用
const html = renderToStaticMarkup(<InteractiveApp />);
// 用户点击按钮不会有任何反应
// ✅ 正确:使用 renderToString 或流式 API
const html = renderToString(<InteractiveApp />);
hydrateRoot(document, <InteractiveApp />);