Back to Heroui

v3.0.0-alpha.35

apps/docs/content/docs/cn/react/releases/v3-0-0-alpha-35.mdx

3.2.411.8 KB
Original Source
<div className="flex items-center gap-3 mb-6"> <span className="text-sm text-muted">2025 年 10 月 21 日</span> </div>

此版本修复了一个关键问题:复合组件在 React Server Components(RSC)中无法正常工作。同时,本版本采用了 React 19 的最佳实践,移除了 forwardRef,并简化了 Context 的使用方式。Switch 组件已经过重构,与 Radio / RadioGroup 模式保持一致,提供更清晰、更统一的 API。

安装

升级到最新版本:

<Tabs items={["npm", "pnpm", "yarn", "bun"]}> <Tab value="npm"> bash npm i @heroui/styles@alpha @heroui/react@alpha </Tab> <Tab value="pnpm"> bash pnpm add @heroui/styles@alpha @heroui/react@alpha </Tab> <Tab value="yarn"> bash yarn add @heroui/styles@alpha @heroui/react@alpha </Tab> <Tab value="bun"> bash bun add @heroui/styles@alpha @heroui/react@alpha </Tab> </Tabs>

<Callout type="info"> **正在使用 AI 助手?** 只需对它说一句「Hey Cursor,把 HeroUI 升级到最新版本」,AI 助手就会自动对比版本并应用必要的变更。了解更多请参阅 [HeroUI MCP 服务器](/docs/ui-for-agents/mcp-server)。 </Callout>

新增功能

React Server Components 支持

复合组件现在可以在 React Server Components 中正常工作。此前的实现把复合模式逻辑放在了组件内部,与 "use client" 指令存在冲突。通过将模式逻辑迁移到组件的索引文件中,这一问题已被修复。

面向 React 19 的改进

本版本采用了 React 19 的最佳实践:

  1. 移除 forwardRef:在 React 19 中已不再需要,ref 现在可以作为普通的 prop 使用(参见 React 19 文档
  2. 简化 Context:将 Context.Provider 替换为直接使用 Context(参见 React 19 文档

Switch 组件架构改进

Switch 组件已经过重构,遵循与 Radio / RadioGroup 相同的清晰拆分模式:

  • 拆分组件:Switch 与 SwitchGroup 现在是独立的组件(此前合并在一起)
  • 更清晰的 API:用 <SwitchGroup> 取代了嵌套的 <Switch.Group><Switch.GroupItems> 模式
  • 更合理的组织:每个组件都有各自独立的样式、类型与实现
  • 一致的模式:与 Radio / RadioGroup 架构保持一致,API 更具可预测性

之前:

tsx
<Switch.Group>
  <Switch.GroupItems>
    <Switch.Root>...</Switch.Root>
  </Switch.GroupItems>
</Switch.Group>

之后:

tsx
<SwitchGroup>
  <Switch.Root>...</Switch.Root>
  <Switch.Root>...</Switch.Root>
</SwitchGroup>

⚠️ 破坏性变更

主组件需要使用 .Root 后缀

为支持 React Server Components,复合组件模式已经过重构。在使用复合写法时,主组件现在需要带上 .Root 后缀。

之前:

tsx
import { Avatar } from "@heroui/react"

<Avatar>
  <Avatar.Image src="/images/avatar.jpeg" alt="Junior Garcia" />
  <Avatar.Fallback>JR</Avatar.Fallback>
</Avatar>

之后:

tsx
import { Avatar } from "@heroui/react"

<Avatar.Root>
  <Avatar.Image src="/images/avatar.jpeg" alt="Junior Garcia" />
  <Avatar.Fallback>JR</Avatar.Fallback>
</Avatar.Root>

说明: 命名导出(例如 <Avatar><AvatarImage><AvatarFallback>)保持不变,依然完全支持。

Switch 组件 API 变更

Switch 组件的分组 API 已经过重构,与 Radio / RadioGroup 模式保持一致:

之前:

tsx
import { Switch } from "@heroui/react"

<Switch.Group orientation="horizontal">
  <Switch.GroupItems>
    <Switch.Root name="notifications">
      <Switch.Control>
        <Switch.Thumb />
      </Switch.Control>
      <Label>Notifications</Label>
    </Switch.Root>
    <Switch.Root name="marketing">
      <Switch.Control>
        <Switch.Thumb />
      </Switch.Control>
      <Label>Marketing</Label>
    </Switch.Root>
  </Switch.GroupItems>
</Switch.Group>

之后:

tsx
import { Switch, SwitchGroup } from "@heroui/react"

<SwitchGroup orientation="horizontal">
  <Switch.Root name="notifications">
    <Switch.Control>
      <Switch.Thumb />
    </Switch.Control>
    <Label>Notifications</Label>
  </Switch.Root>
  <Switch.Root name="marketing">
    <Switch.Control>
      <Switch.Thumb />
    </Switch.Control>
    <Label>Marketing</Label>
  </Switch.Root>
</SwitchGroup>

这次变更带来了:

  • 拆分组件:Switch 与 SwitchGroup 现在是独立的组件(此前合并在一起)
  • 更清晰的 API:用 <SwitchGroup> 取代了嵌套的 <Switch.Group><Switch.GroupItems> 模式
  • 更合理的组织:每个组件都有各自独立的样式、类型与实现
  • 一致的模式:与 Radio / RadioGroup 架构保持一致,API 更具可预测性

迁移步骤:

  1. 单独导入 SwitchGroupimport { Switch, SwitchGroup } from "@heroui/react"
  2. <Switch.Group> 替换为 <SwitchGroup>
  3. 移除嵌套的 <Switch.GroupItems> 包装
  4. 单个的 Switch.Root 组件��持不变

受影响的组件

所有复合组件都受到影响:

  • AccordionAccordion.Root
  • AvatarAvatar.Root
  • CardCard.Root
  • DisclosureDisclosure.Root
  • FieldsetFieldset.Root
  • KbdKbd.Root
  • LinkLink.Root
  • PopoverPopover.Root
  • RadioGroupRadioGroup.Root
  • SwitchSwitch.Root
  • TabsTabs.Root
  • TooltipTooltip.Root

迁移指南

使用 HeroUI 的复合组件有两种选择:

选项 1:改为使用 .Root(复合写法)

如果你使用的是复合写法(点号语法),请将主组件改为使用 .Root

Card 示例:

tsx
import { Card } from "@heroui/react"

<Card.Root>
  <Card.Header>
    <Card.Title>Card Title</Card.Title>
    <Card.Description>Card description</Card.Description>
  </Card.Header>
  <Card.Content>
    Card content
  </Card.Content>
  <Card.Footer>
    Card footer
  </Card.Footer>
</Card.Root>

Tabs 示例:

tsx
import { Tabs } from "@heroui/react"

<Tabs.Root>
  <Tabs.ListWrapper>
    <Tabs.List>
      <Tabs.Tab id="tab1">Tab 1<Tabs.Indicator /></Tabs.Tab>
      <Tabs.Tab id="tab2">Tab 2<Tabs.Indicator /></Tabs.Tab>
    </Tabs.List>
  </Tabs.ListWrapper>
  <Tabs.Panel id="tab1">Panel 1</Tabs.Panel>
  <Tabs.Panel id="tab2">Panel 2</Tabs.Panel>
</Tabs.Root>

更多示例请参阅文档

Avatar 示例:

tsx
import { Avatar } from "@heroui/react"

<Avatar.Root>
  <Avatar.Image alt="John Doe" src="..." />
  <Avatar.Fallback>JD</Avatar.Fallback>
</Avatar.Root>

更多示例请参阅文档

选项 2:使用命名导出

我们已经为所有复合组件添加了命名导出支持,你可以这样使用:

Card 示例:

tsx
import {
  CardRoot,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
  CardFooter,
} from "@heroui/react"

<CardRoot>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
    <CardDescription>Card description</CardDescription>
  </CardHeader>
  <CardContent>
    Card content
  </CardContent>
  <CardFooter>
    Card footer
  </CardFooter>
</CardRoot>

Tabs 示例:

tsx
import { TabsRoot, TabListContainer, TabList, Tab, TabIndicator, TabPanel } from "@heroui/react"

<TabsRoot>
  <TabListContainer>
    <TabList>
      <Tab id="tab1">Tab 1<TabIndicator /></Tab>
      <Tab id="tab2">Tab 2<TabIndicator /></Tab>
    </TabList>
  </TabListContainer>
  <TabPanel id="tab1">Panel 1</TabPanel>
  <TabPanel id="tab2">Panel 2</TabPanel>
</TabsRoot>

Avatar 示例:

tsx
import { Avatar, AvatarImage, AvatarFallback } from "@heroui/react"

<Avatar>
  <AvatarImage alt="John Doe" src="..." />
  <AvatarFallback>JD</AvatarFallback>
</Avatar>

迁移步骤

如果你使用的是复合写法,只需将主组件改为使用 .Root

  1. 查找复合组件的所有使用位置(例如内部包含 <Avatar.Image> 等的 <Avatar>

  2. 为主组件添加 .Root

    tsx
    // Before
    <Avatar>
      <Avatar.Image ... />
    </Avatar>
    
    // After
    <Avatar.Root>
      <Avatar.Image ... />
    </Avatar.Root>
    
  3. 就这样! 所有子组件(如 Avatar.ImageAvatar.Fallback)保持不变。

完整的迁移参考

组件命名导出写法复合写法(带 .Root额外变更
Accordion<Accordion><Accordion.Root>-
Avatar<Avatar><Avatar.Root>-
Card<Card><Card.Root>-
Disclosure<Disclosure><Disclosure.Root>-
Fieldset<Fieldset><Fieldset.Root>-
Kbd<Kbd><Kbd.Root>-
Link<Link><Link.Root>-
Popover<Popover><Popover.Root>-
Radio<Radio><Radio.Root>-
Switch<Switch><SwitchControl><Switch.Root><Switch.Control><Switch.Group><SwitchGroup>(独立组件)
Tabs<Tabs><TabList><Tabs.Root><Tabs.List>-
Tooltip<Tooltip><TooltipTrigger><Tooltip.Root><Tooltip.Trigger>-

自动化迁移

对于使用复合写法的大型代码库,可以借助查找 / 替换:

bash
# Example for Avatar component
# Update the main component to use .Root
sed -i 's/<Avatar>/<Avatar.Root>/g' **/*.tsx
sed -i 's/<\/Avatar>/<\/Avatar.Root>/g' **/*.tsx

# Switch component requires additional steps
# First, ensure SwitchGroup is imported
# Then replace Switch.Group with SwitchGroup
sed -i 's/<Switch\.Group/<SwitchGroup/g' **/*.tsx
sed -i 's/<\/Switch\.Group>/<\/SwitchGroup>/g' **/*.tsx

# Remove Switch.GroupItems wrapper
sed -i 's/<Switch\.GroupItems>//g' **/*.tsx
sed -i 's/<\/Switch\.GroupItems>//g' **/*.tsx

# Repeat for other compound components (Card, Tabs, etc.)
# Note: This only affects files using the compound pattern

重要事项:

  • 使用自动替换时务必小心,确保只替换复合写法的用法,而不要影响命名导出。
  • Switch 的迁移完成后,请确认 SwitchGroup 已被导入:import { Switch, SwitchGroup } from "@heroui/react"
  • 在执行完自动迁移后请测试代码,确认所有变更均符合预期。

为什么需要这次变更?

这次变更是修复 React Server Components 兼容性所必需的。此前的实现存在一些架构上的限制:

  1. RSC 兼容性:复合模式逻辑与 "use client" 指令存在冲突
  2. 拥抱 React 19:移除了 forwardRefContext.Provider 等已被弃用的写法
  3. 更清晰的架构:模式逻辑现在位于索引文件中,而不是组件文件中
  4. 更彻底的拆分:服务端组件与客户端组件现在可以无缝协作

文档更新

组件文档将同步更新,以反映新的写法:

  • 示例将展示带 .Root 的复合写法
  • 命名导出形式的示例依然有效且仍受支持
  • 迁移指南将帮助你顺利完成升级
  • 两种写法都获得完整支持,行为完全一致

需要帮助?

如果你在迁移过程中遇到任何问题:

  1. 复合写法用户:将主组件改为使用 .Root(例如 <Avatar><Avatar.Root>
  2. 命名导出用户:无需做任何修改,你的代码仍可正常工作
  3. 查阅组件文档中的示例
  4. 反馈问题:GitHub Issues

链接

贡献者

感谢每一位为本次发布做出贡献的开发者,是你们让 React Server Components 支持与 React 19 兼容性得到了改进!