首页 / Astro 教程 / React 框架集成示例

Astro 教程

React 框架集成示例

本教程共 56 篇 · 第 29 篇 · 更新于 2026-08-07 · 约 8 分钟阅读

AstroAstro 教程React集成@astrojs/react客户端指令JSX

本节目标:照着做完就能在 Astro 里用上 React 组件,知道 astro.config 与 tsconfig 怎么改,以及多 JSX 框架混用时怎么区分。

安装 @astrojs/react

Astro 提供 astro add 命令,能自动把官方集成装好并改好配置。在项目目录里执行下面任一条,按提示走:

# npm
npx astro add react
# pnpm
pnpm astro add react
# yarn
yarn astro add react

一键安装基本够用。如果它报错或你想完全掌控,就走手动安装。

手动安装

先装包:

npm install @astrojs/react
# 或
pnpm add @astrojs/react
# 或
yarn add @astrojs/react

多数包管理器会顺带把 peer 依赖装好。如果启动 Astro 时看到 Cannot find package 'react' 之类警告,说明你还得手动补上 react 和类型:

npm install react react-dom @types/react @types/react-dom
# 或
pnpm add react react-dom @types/react @types/react-dom
# 或
yarn add react react-dom @types/react @types/react-dom

然后在 astro.config.mjs 里把集成加进 integrations

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';

export default defineConfig({
  // ...
  integrations: [react()],
});

接着在 tsconfig.json 里加上 JSX 相关设置,告诉 TypeScript 用 React 的方式处理:

{
  "extends": "astro/tsconfigs/strict",
  "include": [".astro/types.d.ts", "**/*"],
  "exclude": ["dist"],
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  }
}

第一个 React 组件

装好之后,按「框架组件通用用法」三步走:import、当标签用、需要交互就加 client:* 指令。详细的水合选项和混用嵌套,前面章节已经讲过,这里不再重复。

多 JSX 框架怎么区分

React、Preact、Solid 都常用 .jsx / .tsx 后缀。当你在一个项目里同时接了其中多个,Astro 需要知道「哪个文件归哪个框架」。

单个 JSX 框架时,什么都不用配。多个时,用 include(必填)和 exclude(可选)指定归属。建议把不同框架的组件分目录放,比如 /components/react//components/solid/,配置更省心。

import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import react from '@astrojs/react';
import svelte from '@astrojs/svelte';
import vue from '@astrojs/vue';
import solid from '@astrojs/solid-js';

export default defineConfig({
  // 只用一个 JSX 框架时,不需要 include!
  integrations: [
    preact({ include: ['**/preact/*'] }),
    react({ include: ['**/react/*'] }),
    solid({ include: ['**/solid/*'] }),
  ],
});

children 解析的小坑

从 Astro 组件传给 React 组件的子内容,会被当成「纯字符串」解析,而不是 React 节点。下面这个例子里,ReactComponent 只会收到一个 children

---
import ReactComponent from './ReactComponent';
---

<ReactComponent>
  <div>one</div>
  <div>two</div>
</ReactComponent>

如果你用的某个库期望收到多个子节点(比如要把不同元素塞进不同插槽),这就会卡住。可以打开实验性开关 experimentalReactChildren,让 Astro 始终把 children 当 React 虚拟 DOM 节点传过去。代价是有一点运行时开销,但兼容性更好。

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';

export default defineConfig({
  // ...
  integrations: [
    react({
      experimentalReactChildren: true,
    }),
  ],
});

和 Astro Actions 配合(进阶)

@astrojs/react 提供了两个配合 Astro Actions 的函数:withState()getActionState()。它们和 React 的 useActionState() 钩子一起用,能在表单提交触发 action 时读写客户端状态。

withState() 把 action 和初始状态传给 useActionState()。下面的例子传一个 like action,初始点赞数为 0:

import { actions } from 'astro:actions';
import { withState } from '@astrojs/react/actions';
import { useActionState } from "react";

export function Like({ postId }: { postId: string }) {
  const [state, action, pending] = useActionState(
    withState(actions.like),
    { data: 0, error: undefined }, // 初始点赞数与错误
  );

  return (
    <form action={action}>
      <input type="hidden" name="postId" value={postId} />
      <button disabled={pending}>{state.data} ❤️</button>
    </form>
  );
}

在服务端的 action 处理函数里,可以用 getActionState() 拿到 useActionState() 存的状态。它接受 Astro 的 API 上下文,还能给结果加类型。

import { defineAction, type SafeResult } from 'astro:actions';
import { z } from 'astro/zod';
import { getActionState } from '@astrojs/react/actions';

export const server = {
  like: defineAction({
    input: z.object({ postId: z.string() }),
    handler: async ({ postId }, ctx) => {
      const { data: currentLikes = 0, error } = await getActionState<SafeResult<any, number>>(ctx);
      if (error) throw error;
      return currentLikes + 1;
    },
  }),
};

withState() 会让 action 的类型匹配 React 的预期,并保留「渐进增强」所需的元信息——就算用户设备关了 JS,它也能正常工作。

关闭流式渲染(实验性)

默认 Astro 会对 React 组件做流式输出。有些库(比如部分 CSS-in-JS 方案)和流式不太合得来,可以打开 experimentalDisableStreaming 关掉它。

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';

export default defineConfig({
  integrations: [
    react({
      experimentalDisableStreaming: true,
    }),
  ],
});

写一个 React 计数器组件

装好集成后,在 /src/components 建一个 .jsx 文件,就是一个普通 React 组件:

import { useState } from 'react';

export default function Counter({ initialCount = 0 }) {
  const [count, setCount] = useState(initialCount);
  return (
    <button onClick={() => setCount(count + 1)}>
      点了 {count} 次
    </button>
  );
}

然后在 Astro 页面里 import 它,加 client:load 让它可交互:

---
import Counter from '../components/Counter.jsx';
---

<Counter client:load initialCount={5} />

构建时,Astro 会先在服务端把这个按钮渲染成静态 HTML(显示「点了 5 次」)。页面加载后,框架 JS 下载、水合,按钮才真正能点击。initialCount 这个 prop 会被序列化后传给客户端,所以初始值 5 也能保留。

给 React 组件传 props

从 Astro 传 props 给 React 组件,写法跟普通组件一样。类型得是可序列化的那几种。下面传一个待办数组和一个数字:

---
import TodoList from '../components/TodoList.jsx';
import Counter from '../components/Counter.svelte';
---

<div>
  <TodoList initialTodos={["学 Astro", "审 PR"]} />
  <Counter startingCount={1} />
</div>
Note

字符串、数字、数组、普通对象这类都能传。函数不行——服务端没办法把函数变成客户端能执行的代码。

在 React 里用插槽

React 用名为 children 的 prop 来接子内容。在 Astro 里这么写:

---
import MyReactSidebar from '../components/MyReactSidebar.jsx';
---

<MyReactSidebar>
  <p>这是一个带文字和按钮的侧边栏。</p>
</MyReactSidebar>

用具名插槽时,kebab-case 的名字会转成 camelCase 的 prop:

<MySidebar>
  <h2 slot="title">菜单</h2>
  <p>侧边栏正文</p>
  <ul slot="social-links">
    <li><a href="https://github.com/withastro">GitHub</a></li>
  </ul>
</MySidebar>

在 React 组件里就读 props.titleprops.childrenprops.socialLinks

React 也能只在客户端渲染

如果某个 React 组件完全依赖浏览器、没有服务端 HTML 也能接受,可以用 client:only="react"。它不会在服务端渲染,只在浏览器生成:

<MyReactModal client:only="react" />

代价是首屏那一瞬会有空白,因为没有服务端 HTML 兜底。非必要别用。

接好后不工作?先查这三处

新手接 React 最容易卡在几种情况,按这个顺序排一遍最快:

  1. 组件是静态的、点了没反应:十有八九是忘了加 client:* 指令。没有指令,Astro 只输出静态 HTML,不会发 JS。补上 client:load 之类再试。
  2. 终端报 Cannot find package 'react':peer 依赖没装全。按前面提示把 reactreact-dom 和类型补上。
  3. 组件内容空白或报错,多 JSX 框架共存时:检查 astro.config.mjsinclude 目录是否把文件指对了框架。归错框架,编译就会出问题。

还有一类隐蔽问题:从 Astro 传过去的 props 必须可序列化。如果你把「函数」当 prop 传进 React 组件,运行时不会报错在编译期,但水合后那部分逻辑会失效。遇到「交互只做了一半」,先确认没传函数之类的不可序列化数据。

React 在 Astro 里的定位

把 React 接进 Astro,不是把整站改成 React 应用,而是「在静态海洋里放几个 React 岛屿」。其余页面、布局、文章依然是 Astro 原生组件,零框架负担。

这种定位很灵活。你可以只在一个需要复杂状态的角落用 React,比如一个带筛选的数据表格;其他所有地方都不碰 React。这样既享受了 React 的生态,又不牺牲 Astro 的快。

水合过程通俗讲一遍

「水合」听上去玄,其实就三步。第一步,Astro 在服务端把 React 组件渲染成静态 HTML,用户立刻看到内容。第二步,浏览器下载这个组件对应的 JS(含 React 运行时)。第三步,JS 把这段静态 HTML「接上电」,让它变成能点击、能响应事件的活组件。

关键在于:第一步的静态 HTML 已经够看、够读。JS 只是锦上添花,让它能交互。就算 JS 慢一点,用户也不会面对空白页。这就是「默认快」的来源。

别忘了心智负担

多框架共存也有代价。团队成员得知道哪些文件是 React、哪些是 Svelte;构建配置要维护正确。如果你只用一种框架,事情最简单。只有真需要「一个项目里混多种技术」时,才值得上多框架集成。

一点心里话

学完这一章你会发现,接 React 本身不难,难的是「克制」。Astro 的魅力恰恰在于:它不强迫你用框架,也不阻止你用。什么时候该把内容做成静态 HTML、什么时候该升级成 React 岛屿,这个分寸感,才是在 Astro 里用好 React 的关键。

版本小提示

写这篇时官方 React 集成版本是 v6.0.2,配 Astro 7 使用没问题。框架和集成都会迭代,若你将来看到不同版本号,别慌,安装命令和 client:* 用法基本不会变。唯一要留意的,是 astro.config.mjsintegrations 那行和 tsconfig 的 JSX 设置,按你当时装的文档照抄就行。

小结

React 集成就是「装包、改 astro.config、改 tsconfig」三件事。多 JSX 框架共存时靠 include 区分目录。下一章我们看 Vue、Svelte、Preact、Solid 各自怎么接。