作者 Dan Abramov

面向 Astro 开发者的 RSC 指南

2025年5月6日 随意赞助

好吧,在 Astro 中,你有两种东西:

这是一个 PostPreview.astro Astro Component 渲染一个 LikeButton Island 的示例:

---
import { readFile } from 'fs/promises';
import { LikeButton } from './LikeButton';
const { slug } = Astro.props;
const title = await readFile(`./posts/${slug}/title.txt`, 'utf8');
---
<article>
 <h1>{title}</h1>
 <LikeButton client:load />
</article>
import { useState } from 'react';
export function LikeButton() {
 const [liked, setLiked] = useState(false);
 return (
  <button onClick={() => setLiked(!liked)}>
   {liked ? '❤️' : '🤍'} Like
  </button>
 );
}

请注意,Astro Components 和 Client Islands 本质上生活在两个不同的“世界”中,并且数据始终向下流动。 Astro Components 是所有预处理发生的地方; 它们将交互位“移交给” Client Islands。 现在让我们看看 React Server Components (RSC)。

在 RSC 中,相同的两个东西被称为 Server ComponentsClient Components 这是您将上述 Astro Component 编写为 React Server Component 的方式:

import { readFile } from 'fs/promises';
import { LikeButton } from './LikeButton';
async function PostPreview({ slug }) {
 const title = await readFile(`./posts/${slug}/title.txt`, 'utf8');
 return (
  <article>
   <h1>{title}</h1>
   <LikeButton />
  </article>
 );
}
'use client';
import { useState } from 'react';
export function LikeButton() {
 const [liked, setLiked] = useState(false);
 return (
  <button onClick={() => setLiked(!liked)}>
   {liked ? '❤️' : '🤍'} Like
  </button>
 );
}

这两种背后的心智模型非常相似! 如果你了解 Astro,你就已经掌握了 React Server Components 80% 的心智模型。(即使你认为 React Server Components 是一个糟糕的主意,Astro 也值得学习。) 让我们注意一下您可能注意到的几个语法差异:

最后一点很有意思。 在 Astro 中,不同的语法(.astro 文件与 Client Islands)在两个世界之间创建了鲜明且明显的视觉区别。 同一个组件不能同时充当 Astro Component Client Island,具体取决于上下文——它们是具有不同语法的两个不同的事物。

但是在 RSC 中,“Astro”部分也“只是 React”。 因此,如果你有一个不使用任何客户端特定_或_服务器特定功能的组件,它可以扮演这两种角色。

考虑一个 <Markdown /> 组件,它执行自己的解析。 由于它不使用任何客户端功能(没有 State)或任何服务器功能(没有读取 DB),因此可以在任何一侧导入它。 因此,如果你从 Server 世界导入它,它将像一个“Astro Component”一样行动,但如果你从 Client 世界导入它,它将像一个“Client Island”一样行动。 这不是什么新概念,这只是导入函数的工作方式!

在 RSC 中,从 Server 世界导入的东西将在 Server 世界中运行; 从 Client 世界导入的东西将在 Client 世界中运行; 并且在任何一个世界中都不支持的东西(例如 Client 上的 DB 或 Server 上的 useState)会导致构建错误,因此你将被_迫_使用 'use client' “切开一扇门”。

这既是祝福也是诅咒。

这是一个诅咒,因为它使得学习使用 RSC 相当不直观。 你一直担心“你身处哪个世界”。 需要练习才能接受它_并不重要_,因为你总是可以在本地进行推理。 你可以在需要它们的server端文件中使用 DB 等server端功能,在需要它们的client端文件中使用 State 等client端功能,并依靠构建时断言,如果出现问题会导致错误。 然后你查看模块堆栈跟踪,并决定在哪里为你的“islands” “切开一扇新门”。

这_确实_是一个诅咒,但它也是一种祝福。 通过在两端都拥抱 React,RSC 模型解决了你可能遇到的 Astro 的一些限制:

在 Astro 中,基本输出格式是 HTML。 由于前端框架从根本上不操作 HTML 本身(它们操作一个有状态的 DOM,可以用 HTML 初始化),因此 Astro 遵循“一次性移交”模型。 这使得它在某种程度上更容易学习,但将服务器功能限制为“首次渲染”(到 HTML)所需的功能,并且主要让你自己处理交互位。 随着你使更多事物具有交互性,你可能会发现自己遇到了 Astro 模型的局限性,可能选择将更多逻辑移动到类似 SPA 的但孤立的 Islands 中。

在 RSC 中,基本输出格式是 React 树(可以转换为 HTML,但也可以作为 JSON(重新)获取)。 由于 RSC 在两端都使用 React,并且两个世界之间没有视觉区别,因此学习使用它更具挑战性。 好处是,一旦你掌握了移动边界的窍门,它们就会变得非常流畅,并解决了你必须将代码“移入 Astro”或“移回 Islands”的问题,因为某些东西最终变得比预期的更静态或更动态。 你还可以保留相同的“只是将数据映射到 UI”的心智模型,无论 UI 是只读的还是需要响应突变而重新获取。 Server 部分深入到树中——与_它们的_ Client 部分交织在一起

并且因为两端都是 React,所以所有 React 功能都端到端集成:例如,Client 上的 <Suspense> 声明性加载状态 将“知道”等待异步数据(来自 Server)、JS 和 CSS(当 Client 加载它们时)、字体和图像(具有合理的超时),甚至触发 View Transitions(请参阅 此处)。 在 React 中,每个功能的都经过设计,因此 Server 和 Client 片段可以任意嵌套、组合和就地刷新。 这是一个单一的树。 缺点是,购买 RSC 意味着购买 React。 RSC _是_全栈 React。

最后,值得注意的是,Astro 是一个框架,但 RSC 本身是较低级别的——可以将其视为框架的构建块,或框架可以实现的标准。 目前 RSC 的两个官方支持的实现包括 Next.js App Router(一个框架)和 Parcel RSC(不是框架)。

就我个人而言,我认为 RSC 的开发者体验仍然有些粗糙,但我也认为你可能无论如何都想学习它。 它有一些有趣的想法。

此外,如果你从未使用过 Astro,请尝试一下! 如果 RSC 让你感到困难,Astro 可能会为相同的想法提供更温和的入门途径。 而且如果你只使用过客户端 React,Astro 可能会解决你从未意识到自己存在的一些问题。 随意赞助 在 Bluesky 上讨论 · 在 GitHub 上编辑 overreacted