Skip to content

第2章:React Server 技术演进历程

2.1 React SSR 发展时间线

技术演进时间轴

2.2 React 15-16 时代:传统 SSR 的奠基

React 15 的 SSR 实现

在 React 15 时代,SSR 的实现相对简单但功能有限:

javascript
// React 15 SSR 示例
import React from 'react';
import { renderToString } from 'react-dom/server';
import express from 'express';

const app = express();

// 简单的组件
class App extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, {this.props.name}!</h1>
        <p>Welcome to React 15 SSR</p>
      </div>
    );
  }
}

app.get('/', (req, res) => {
  // 同步渲染,阻塞式
  const html = renderToString(<App name="World" />);
  
  res.send(`
    <!DOCTYPE html>
    <html>
      <head><title>React 15 SSR</title></head>
      <body>
        <div id="root">${html}</div>
        <script src="/bundle.js"></script>
      </body>
    </html>
  `);
});

React 15 SSR 的限制:

  1. 同步渲染renderToString 是同步的,会阻塞事件循环
  2. 无流式支持:必须等待整个组件树渲染完成
  3. 错误处理简陋:一个组件出错会导致整个渲染失败
  4. 性能问题:大型应用渲染时间长

React 16 Fiber 架构的革命

React 16 引入的 SSR 改进:

javascript
// React 16 流式 SSR
import { renderToNodeStream } from 'react-dom/server';
import { PassThrough } from 'stream';

app.get('/', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  
  // 发送 HTML 头部
  res.write(`
    <!DOCTYPE html>
    <html>
      <head><title>React 16 Streaming SSR</title></head>
      <body>
        <div id="root">
  `);
  
  // 流式渲染组件
  const stream = renderToNodeStream(<App />);
  
  stream.pipe(res, { end: false });
  
  stream.on('end', () => {
    res.write(`
        </div>
        <script src="/bundle.js"></script>
      </body>
    </html>
    `);
    res.end();
  });
  
  stream.on('error', (error) => {
    console.error('SSR Error:', error);
    res.end('Server Error');
  });
});

2.3 React 18 时代:Concurrent Features 与 Streaming SSR

Concurrent Features 架构图

React 18 Streaming SSR 详解

javascript
// React 18 现代 SSR 实现
import { renderToPipeableStream } from 'react-dom/server';
import { Suspense } from 'react';

// 支持 Suspense 的组件
function App() {
  return (
    <html>
      <head>
        <title>React 18 Streaming SSR</title>
      </head>
      <body>
        <div id="root">
          <Header />
          <Suspense fallback={<div>Loading main content...</div>}>
            <MainContent />
          </Suspense>
          <Suspense fallback={<div>Loading sidebar...</div>}>
            <Sidebar />
          </Suspense>
          <Footer />
        </div>
      </body>
    </html>
  );
}

// 异步组件示例
async function MainContent() {
  // 模拟数据获取
  const data = await fetchMainData();
  return <main>{data.content}</main>;
}

async function Sidebar() {
  const data = await fetchSidebarData();
  return <aside>{data.widgets}</aside>;
}

// 服务器端渲染
app.get('/', (req, res) => {
  const stream = renderToPipeableStream(<App />, {
    // Shell 准备就绪时立即发送
    onShellReady() {
      res.statusCode = 200;
      res.setHeader('Content-type', 'text/html');
      stream.pipe(res);
    },
    
    // 所有内容准备就绪
    onAllReady() {
      // 对于爬虫,等待所有内容
      if (req.headers['user-agent']?.includes('bot')) {
        res.statusCode = 200;
        res.setHeader('Content-type', 'text/html');
        stream.pipe(res);
      }
    },
    
    // 错误处理
    onError(error) {
      console.error('SSR Error:', error);
      res.statusCode = 500;
    }
  });
});

Streaming SSR 工作流程

选择性 Hydration (Selective Hydration)

javascript
// 客户端 Hydration 代码
import { hydrateRoot } from 'react-dom/client';
import { Suspense } from 'react';

// React 18 会智能地选择 Hydration 顺序
function ClientApp() {
  return (
    <div>
      <Header /> {/* 立即 Hydration */}
      
      <Suspense fallback={<div>Loading...</div>}>
        <MainContent /> {/* 数据到达后 Hydration */}
      </Suspense>
      
      <Suspense fallback={<div>Loading...</div>}>
        <InteractiveWidget /> {/* 用户交互时优先 Hydration */}
      </Suspense>
      
      <Footer /> {/* 最后 Hydration */}
    </div>
  );
}

// 客户端入口
const root = hydrateRoot(document.getElementById('root'), <ClientApp />);

选择性 Hydration 的优势:

  1. 优先级驱动:用户交互的组件优先 Hydration
  2. 非阻塞:不需要等待所有组件都准备好
  3. 渐进式:页面逐步变得可交互
  4. 性能优化:减少 TTI (Time to Interactive)

2.4 React Server Components 的诞生

RSC 概念的提出背景

RSC 与传统 SSR 的根本区别

RSC 架构设计原理

React Flight Protocol 详解

javascript
// Flight Protocol 数据格式示例
// 这是 RSC 序列化后的数据格式

// 1. 服务器组件渲染结果
const flightData = {
  // 组件树结构
  tree: {
    type: 'div',
    props: {
      children: [
        {
          type: 'h1',
          props: { children: 'Welcome to RSC' }
        },
        {
          // 客户端组件引用
          type: Symbol.for('react.client.reference'),
          props: { userId: 123 },
          $$typeof: Symbol.for('react.element'),
          _payload: {
            name: 'InteractiveButton',
            id: './components/InteractiveButton.js'
          }
        }
      ]
    }
  },
  
  // 客户端组件映射
  clientReferences: {
    './components/InteractiveButton.js': {
      id: 'chunk-123',
      chunks: ['chunk-123.js'],
      name: 'InteractiveButton'
    }
  }
};

// 2. 客户端接收和处理
function processFlightData(flightData) {
  // 反序列化组件树
  const tree = deserializeTree(flightData.tree);
  
  // 加载客户端组件
  const clientComponents = loadClientComponents(
    flightData.clientReferences
  );
  
  // 渲染混合组件树
  return renderMixedTree(tree, clientComponents);
}

2.5 现代框架的 RSC 集成

Next.js App Router 架构

Next.js RSC 实现示例

javascript
// app/page.js - Server Component
import { Suspense } from 'react';
import { UserProfile } from './components/UserProfile';
import { InteractiveChat } from './components/InteractiveChat';

// 这是一个 Server Component
export default async function HomePage({ searchParams }) {
  // 直接在服务器获取数据
  const user = await db.users.findById(searchParams.userId);
  const posts = await db.posts.getRecent(10);
  
  return (
    <div className="homepage">
      <header>
        <h1>欢迎回来, {user.name}!</h1>
      </header>
      
      <main>
        {/* Server Component - 不会发送到客户端 */}
        <UserProfile user={user} />
        
        {/* Suspense 边界 */}
        <Suspense fallback={<div>加载帖子中...</div>}>
          <PostList posts={posts} />
        </Suspense>
        
        {/* Client Component - 需要交互 */}
        <InteractiveChat userId={user.id} />
      </main>
    </div>
  );
}

// app/components/UserProfile.js - Server Component
export async function UserProfile({ user }) {
  // 可以直接访问数据库或 API
  const stats = await db.users.getStats(user.id);
  
  return (
    <section className="user-profile">
      <img src={user.avatar} alt={user.name} />
      <div>
        <h2>{user.name}</h2>
        <p>帖子: {stats.postCount}</p>
        <p>关注者: {stats.followers}</p>
      </div>
    </section>
  );
}

// app/components/InteractiveChat.js - Client Component
'use client';
import { useState, useEffect } from 'react';

export function InteractiveChat({ userId }) {
  const [messages, setMessages] = useState([]);
  const [newMessage, setNewMessage] = useState('');
  
  useEffect(() => {
    // 建立 WebSocket 连接
    const ws = new WebSocket(`/api/chat/${userId}`);
    
    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      setMessages(prev => [...prev, message]);
    };
    
    return () => ws.close();
  }, [userId]);
  
  const sendMessage = () => {
    if (newMessage.trim()) {
      // 发送消息逻辑
      fetch('/api/chat/send', {
        method: 'POST',
        body: JSON.stringify({ userId, message: newMessage })
      });
      setNewMessage('');
    }
  };
  
  return (
    <div className="chat-widget">
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id} className="message">
            {msg.content}
          </div>
        ))}
      </div>
      
      <div className="input-area">
        <input
          value={newMessage}
          onChange={(e) => setNewMessage(e.target.value)}
          placeholder="输入消息..."
        />
        <button onClick={sendMessage}>发送</button>
      </div>
    </div>
  );
}

构建过程详解

2.6 技术演进的关键节点总结

性能指标对比

开发体验演进

特性React 15React 16React 18RSC
渲染方式同步阻塞流式渲染并发渲染混合渲染
错误处理基础Error Boundaries改进细粒度
代码分割手动动态导入Suspense自动
数据获取客户端混合改进服务器优先
Bundle 大小最小化
开发复杂度中等中等较高简化

未来发展趋势

小结

本章回顾了 React Server 技术从 React 15 到现在的完整演进历程:

  1. React 15-16:奠定了 SSR 的基础,引入了流式渲染
  2. React 18:带来了并发特性和选择性 Hydration
  3. RSC 时代:革命性地改变了前后端代码的组织方式
  4. 现代框架:Next.js 等框架让 RSC 在生产环境中可用

下一章我们将深入探讨 React Server 的基础概念,包括组件分类、渲染机制等核心知识。


微信公众号二维码