React 服务端渲染深度
1. 渲染模式对比
| 模式 | 全称 | 渲染时机 | 适用场景 |
|---|---|---|---|
| CSR | Client-Side Rendering | 浏览器运行时 | 后台管理、SPA |
| SSR | Server-Side Rendering | 每次请求时 | 动态内容、SEO |
| SSG | Static Site Generation | 构建时 | 博客、文档 |
| ISR | Incremental Static Regen | 构建时 + 按需更新 | 电商、新闻 |
2. SSR 原理
传统 SSR 流程
Request → renderToString() → HTML String → Response
↓
浏览器接收 HTML → hydrate() → 可交互问题: TTFB 慢
renderToString 必须渲染完整个组件树才能响应。
3. Streaming SSR (React 18)
核心 API
javascript
import { renderToPipeableStream } from 'react-dom/server';
app.get('/', (req, res) => {
const stream = renderToPipeableStream(
<App />,
{
bootstrapScripts: ['/main.js'],
onShellReady() {
// Shell (Suspense 外的内容) 准备好
res.setHeader('Content-Type', 'text/html');
stream.pipe(res);
},
onAllReady() {
// 所有内容 (含 Suspense 内) 准备好
}
}
);
});工作原理
- Shell 先发送: Suspense 外的内容立即发送
- 流式发送: Suspense 内容准备好后,通过
<script>注入 - Selective Hydration: 优先 hydrate 用户交互的部分
HTML 输出示例
html
<!-- 初始 Shell -->
<div id="root">
<header>...</header>
<Suspense fallback="Loading...">
<!--$?--><template id="B:0"></template><!--/$-->
</Suspense>
</div>
<!-- 流式注入 (稍后) -->
<script>
$RC = function(a, b) { /* 替换内容 */ };
$RC("B:0", "<div>Loaded Content</div>");
</script>4. React Server Components (RSC)
核心思想
- Server Component: 只在服务端运行,不发送 JS 到客户端
- Client Component: 传统 React 组件 (
'use client')
约束
| Server Component | Client Component | |
|---|---|---|
| 使用 useState/useEffect | ❌ | ✅ |
| 使用浏览器 API | ❌ | ✅ |
| 直接访问数据库/文件系统 | ✅ | ❌ |
| 发送 JS 到客户端 | ❌ | ✅ |
| 可 import Client Component | ✅ | ✅ |
| 可 import Server Component | ✅ | ❌ |
示例
jsx
// app/page.tsx (Server Component by default)
async function Page() {
// 直接访问数据库!
const posts = await db.query('SELECT * FROM posts');
return (
<div>
{posts.map(post => (
<Article key={post.id} post={post} />
))}
{/* Client Component 处理交互 */}
<LikeButton />
</div>
);
}jsx
// components/LikeButton.tsx
'use client';
export function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(true)}>Like</button>;
}优势
- 减少 Bundle Size: Server Component 不发送 JS
- 直接访问后端资源: 无需 API 层
- 自动代码分割: 按组件边界分割
5. Hydration Mismatch 问题
常见原因
- 服务端渲染时间 (
new Date()) 与客户端不同 - 使用
window对象 (服务端不存在) - 第三方库不兼容 SSR
解决方案
jsx
// 1. 使用 useEffect 延迟客户端渲染
function ClientOnlyComponent() {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return null;
return <div>{window.innerWidth}</div>;
}
// 2. suppressHydrationWarning (不推荐)
<time suppressHydrationWarning>{new Date().toISOString()}</time>