Featured image of post React 服务器组件:Web 渲染的未来Featured image of post React 服务器组件:Web 渲染的未来

React 服务器组件:Web 渲染的未来

探索 React Server Components 架构、服务器与客户端边界、流式 SSR、数据获取模式、Suspense 集成以及现有项目的采用策略。

React 服务器组件 (RSC) 代表了 React 应用程序渲染方式的范式转变。组件专门在服务器上运行,向客户端发送零 JavaScript。这与服务器端渲染 (SSR) 不同,后者在服务器上渲染组件,但仍将它们的所有 JavaScript 发送到客户端进行水合作用。 RSC 生成一种特殊的序列化格式——RSC 有效负载——客户端上的 React 协调器使用它来重建组件树,而不执行组件代码。

核心好处是客户端 JavaScript 的显着减少。不需要交互性的布局组件、数据获取逻辑和表示元素可以完全在服务器上执行。更小的捆绑包意味着更快的页面加载、更低的内存使用以及改进的 Core Web Vitals。

服务器与客户端的边界

指令 'use client''use server' 定义边界。在支持 RSC(Next.js App Router、Hydrogen 等)的框架中,任何没有指令的组件默认都是服务器组件。

// This is a server component by default
import { db } from "@/lib/db";

export default async function ProductList() {
  const products = await db.query("SELECT * FROM products");
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}
// This is a client component
"use client";

import { useState } from "react";

export default function LikeButton({ productId }) {
  const [liked, setLiked] = useState(false);
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? "♥" : "♡"}
    </button>
  );
}

关键规则:服务器组件不能使用状态、效果或浏览器 API。从服务器传递到客户端的道具必须是可序列化的——没有函数,没有类实例,没有循环引用。客户端组件可以通过组合导入服务器组件(将服务器组件作为子组件或 props 传递),但不能直接导入它们。


流媒体 SSR 和悬念

RSC 可以开箱即用地进行流式传输。当页面使用 Suspense 边界时,每个边界可以在其数据可用时独立进行流式传输,而无需等待整个页面呈现。

export default function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<ProductSkeleton />}>
        <ProductList />
      </Suspense>
      <Suspense fallback={<ReviewSkeleton />}>
        <Reviews />
      </Suspense>
    </div>
  );
}

在此示例中, Header 立即呈现, ProductList 在其数据库查询完成时呈现, Reviews 在其查询完成时呈现。浏览器会逐步显示内容,而不是显示空白页面,直到一切准备就绪。这改进了首字节时间 (TTFB) 和最大内容绘制 (LCP)。

公制传统SSR流媒体 RSC
TTFB整页渲染后第一个悬念边界之后
液晶聚合物补水后第一个流式传输块之后
JS 捆绑完整的应用程序包仅客户端组件
互动时间充分补水后页面交互更快

数据获取模式

RSC 最具变革性的功能是直接在组件中获取数据,无需 API 层。数据库查询与使用它们的组件位于同一位置,从而消除了客户端数据获取中常见的瀑布问题。

async function ProductDetails({ id }) {
  const product = await db.query(
    "SELECT * FROM products WHERE id = $1",
    [id]
  );

  const reviews = await db.query(
    "SELECT * FROM reviews WHERE product_id = $1",
    [id]
  );

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <ReviewList reviews={reviews} />
    </div>
  );
}

React 在渲染期间自动删除使用 fetch() 发出的重复请求。 React.cache() 函数将此重复数据删除功能扩展到任何异步操作,包括数据库查询。对于重新验证,RSC 使用基于时间 (ISR)、按需 (webhooks) 和突变触发的策略。


服务器操作的突变

使用 'use server' 指令定义的服务器操作可以处理表单提交和数据突变,而无需构建 API 路由。

async function createProduct(formData) {
  "use server";

  const name = formData.get("name");
  const price = formData.get("price");

  await db.query(
    "INSERT INTO products (name, price) VALUES ($1, $2)",
    [name, price]
  );

  revalidatePath("/products");
}

export default function ProductForm() {
  return (
    <form action={createProduct}>
      <input name="name" required />
      <input name="price" type="number" required />
      <button type="submit">Create</button>
    </form>
  );
}

服务器操作支持渐进式增强——该表单无需 JavaScript 即可运行。当 JavaScript 可用时,操作通过 fetch 提交,服务器仅重新呈现受影响的组件。错误处理和乐观更新是通过客户端钩子(例如 React 19 中的 useActionState )进行管理的。


采用策略

对于现有的 Next.js 项目,从 Pages Router 到 App Router 的迁移路径是增量的。您可以在迁移期间并排运行两个路由器:

  1. 从数据组件开始:首先将静态或数据量大的页面移至 RSC
  2. 识别客户端边界:任何使用状态、效果或事件处理程序的组件都会变成 'use client'
  3. 提取交互岛:保持客户端组件较小且处于叶级
  4. 上移数据获取:将数据获取推送到父服务器组件,将结果作为 props 向下传递

并非所有应用程序都能受益于 RSC。高度交互的仪表板、实时协作工具以及大多数组件使用状态和效果的应用程序可能获得的好处微乎其微。在这些情况下,客户端渲染或传统 SSR 可能更合适。


性能和捆绑影响

RSC 最可衡量的影响是 JavaScript 捆绑包的减少。以前作为 JavaScript 发送到客户端的布局组件、文本较多的页面和数据获取逻辑现在仅在服务器上执行。 RSC 有效负载格式是一种紧凑的二进制表示形式,客户端上的 React 协调器可以有效地处理它。

网络选项卡分析将 RSC 响应显示为特殊 RSC 内容类型的流式传输块。每个块对应于一个正在解决的 Suspense 边界。这种流粒度意味着浏览器增量地接收和呈现内容,从而提高感知性能,超出了 JavaScript 捆绑测量单独建议的范围。

现状与展望

截至 2024 年,React Server Components 已坚定地进入生产领域。 Next.js 14 以 RSC 为核心稳定了 App Router。 React 团队继续完善模型,致力于开发与 RSC 兼容的状态管理解决方案和改进的工具。 Hydrogen (Shopify) 和 RedwoodJS 等新框架已采用 RSC 作为其主要渲染模型。

持续的争论集中在复杂性上——RSC 引入了一种新的思维模型,需要时间来学习,并且服务器/客户端边界需要仔细的架构决策。然而,对于内容丰富的网站、电子商务平台和页面加载性能至关重要的应用程序来说,减少 JavaScript 和改进流传输的好处远远超过了学习曲线。