Arco Design 折叠面板 Collapse 组件完全指南:API 详解、交互模式与源码原理

原创2026-10-12 13:07:151,156 阅读
文章标签:UI组件前端设计系统

Arco Design 折叠面板 Collapse 组件完全指南:API 详解、交互模式与源码原理

折叠面板(Collapse)是数据展示场景中最常用的内容收纳组件之一,它允许用户将复杂的内容区域分组并自由折叠/展开,从而在有限空间内承载大量信息。在 Arco Design React 组件库中,Collapse 组件不仅提供了基础的面板折叠能力,还内置了手风琴模式、懒加载、隐藏时销毁、自定义触发区域等丰富的交互配置,并且支持键盘操作与无障碍语义。本文将以 components/Collapse/README.en-US.md 的 API 文档为骨架,结合仓库内源码(collapse.tsx、item.tsx、interface.ts)与演示用例,完整讲解 Collapse 的每一个配置项、交互细节与底层实现原理,帮助你在一线项目中熟练运用并深度定制该组件。

一、组件概览与适用场景

在 Arco Design 中,Collapse 属于「数据展示(Data Display)」类组件,官方定位为「可以折叠 / 展开的内容区域」。它非常适合以下几类场景:

  • 将冗长的说明文本、表单分组收纳为可展开的区块,保持页面整洁;
  • 构建「常见问题(FAQ)」式的问答列表;
  • 实现「手风琴」式的单选展开交互(如设置页的分组导航);
  • 作为嵌套的二级 / 多级目录结构。

组件的入口位于 components/Collapse/index.tsx,对外导出 Collapse 组件本体以及 CollapseProps、CollapseItemProps 两个类型。与多数 Arco 组件一样,Collapse.Item 被挂载为 Collapse 的静态属性(见 collapse.tsx),因此你可以使用 Collapse.Item 或先解构 const CollapseItem = Collapse.Item 再使用。

二、快速上手:基础用法

最基本的使用方式是把若干个 Collapse.Item(下面简称面板)放进 Collapse 容器中。每个面板通过 name 声明自己的唯一标识,通过 header 指定头部内容,面板体(children)就是折叠 / 展开的内容区域。

官方示例(demo/basic.md):

import { Collapse, Divider } from '@arco-design/web-react';
const CollapseItem = Collapse.Item;

const App = () => {
  return (
    <Collapse defaultActiveKey={['1', '2']} style={{ maxWidth: 1180 }}>
      <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="1">
        Beijing Toutiao Technology Co., Ltd.
        <Divider style={{ margin: '8px 0' }} />
        Beijing Toutiao Technology Co., Ltd.
      </CollapseItem>

      <CollapseItem header="Introduce" name="2" disabled>
        ByteDance's core product, Toutiao ("Headlines"), is a content platform...
      </CollapseItem>

      <CollapseItem header="The Underlying AI Technology" name="3">
        In 2016, ByteDance's AI Lab and Peking University co-developed Xiaomingbot...
      </CollapseItem>
    </Collapse>
  );
};

export default App;

要点说明:

  • name 是必填的字符串属性,它既用于与 activeKey / defaultActiveKey 对应,也是 onChange 回调中 key 参数的取值来源;
  • defaultActiveKey={['1', '2']} 表示组件首次渲染时默认展开 name 为 '1'、'2' 的两个面板;
  • 默认情况下多个面板可以同时展开,也可以只展开某几个;
  • 面板 2 设置了 disabled,因此它不会被用户展开或折叠(同时样式上会呈现禁用态)。

从源码看(collapse.tsx),activeKey 与 defaultActiveKey 通过 useMergeValue 实现受控/非受控的自动切换:当 props 中存在 activeKey 时组件表现为受控组件,否则组件内部自行维护展开状态,初始值取自 defaultActiveKey。

三、受控与手风琴模式

3.1 手风琴模式(accordion)

手风琴模式是折叠面板最经典的交互形式:同一时刻只允许展开一个面板,展开新面板时自动收起当前面板。只需要在 Collapse 上添加 accordion 属性即可(示例见 demo/accordion.md):

<Collapse accordion style={{ maxWidth: 1180 }}>
  <CollapseItem header="Panel 1" name="1">...</CollapseItem>
  <CollapseItem header="Panel 2" name="2">...</CollapseItem>
  <CollapseItem header="Panel 3" name="3">...</CollapseItem>
</Collapse>

手风琴模式的核心处理逻辑在源码的 getActiveKeys 与 onItemClick 中:

  • 初始化时(collapse.tsx):getActiveKeys 会把传入的 keys 收拢为数组,并在 accordion 为 true 时只保留第一个,即 res.slice(0, 1);
  • 点击切换时(collapse.tsx):如果当前点击的面板不在激活列表里且处于手风琴模式,则直接把激活列表重置为 [key],从而实现「一次只展开一个」。

对应的测试用例(test/index.test.tsx 中 'expand and collapse correctly when accordion' 用例)验证了:依次点击全部面板后,onChange 回调收到的 keys 数组长度恒为 1,且值为最后一个被点击面板的 name。

3.2 受控使用与 onChange

当需要把展开状态交由业务代码管理时,使用 activeKey + onChange 即可。onChange 的回调签名为:

(key: string, keys: string[], e) => void
  • key:本次被点击切换的面板 name;
  • keys:切换后的完整激活面板数组;
  • e:触发事件对象。

一个典型的受控写法:

const [active, setActive] = React.useState(['1']);

<Collapse
  activeKey={active}
  onChange={(key, keys) => setActive(keys)}
>
  {/* CollapseItem 省略 */}
</Collapse>

值得注意的源码细节是:在受控模式下('activeKey' in props 为真),点击只会回调 onChange 而不会修改内部状态(见 collapse.tsx),状态同步完全由父组件驱动,这符合 React 受控组件的一贯约定。

3.3 嵌套面板

面板内容区可以继续嵌套 Collapse,实现多级结构。官方示例(demo/inline.md)展示了如何在面板 1 的内容中再放一个 Collapse,并单独指定内层的 defaultActiveKey:

<Collapse defaultActiveKey={['1', '2']}>
  <CollapseItem header="Outer Panel" name="1">
    <Collapse defaultActiveKey={'1.1'}>
      <CollapseItem header="Inner Panel A" name="1.1">...</CollapseItem>
      <CollapseItem header="Inner Panel B" name="1.2">...</CollapseItem>
    </Collapse>
  </CollapseItem>
  ...
</Collapse>

由于内层 Collapse 独立维护自己的 CollapseContext,内外层的展开状态互不干扰,name 也可以放心使用如 1.1 这样的层级化命名。

四、展开图标:位置、显隐与自定义

Collapse 与 Collapse.Item 都支持展开图标的定制,相关属性包括:

属性 所属组件 说明
expandIconPosition Collapse 展开图标的位置,'left' | 'right',默认 left
expandIcon Collapse / Collapse.Item 自定义展开图标(ReactNode)
showExpandIcon Collapse.Item 是否展示展开按钮,默认 true

4.1 图标位置

通过 expandIconPosition="right" 可以把展开图标移到面板头部右侧(示例见 demo/iconPosition.md)。从实现上看(collapse.tsx),当没有自定义 expandIcon 时,组件会根据位置自动选择内置图标:left 使用 IconCaretRight,right 使用 IconCaretLeft。

样式层面对应的处理见 style/index.less:-header-left 会在左侧预留图标占位的内边距,-header-right 则在右侧预留,保证图标与文字间距一致;图标旋转动画通过 -header-icon-down 的 rotate(90deg) 实现。

4.2 自定义图标与隐藏图标

Collapse.expandIcon 是全局图标,Collapse.Item.expandIcon 只作用于单个面板。优先级逻辑在 item.tsx:

const icon = showExpandIcon ? ('expandIcon' in props ? expandIcon : ctx.expandIcon) : null;

即:面板自身的 expandIcon 优先于父级 Collapse 的 expandIcon;showExpandIcon={false} 时该面板完全不渲染图标。测试用例('collapse expandIcon')验证了:自定义图标会渲染为 .arco-icon-plus,隐藏图标的面板会带有 arco-collapse-item-no-icon 类名。

实际开发中,把 Collapse 的 expandIcon 设为 null 即可全局隐藏所有图标(官方 demo 中通过 Radio 动态切换「hide / left / right」三种形态)。

五、渲染策略:lazyload 与 destroyOnHide

这是 Collapse 面向性能优化的两个关键属性,很容易混淆,这里结合源码讲透。

5.1 lazyload(懒加载)

  • 默认值:true
  • 作用:设置为 true 时,挂载阶段不渲染被隐藏(折叠)面板的内容,直到该面板第一次被展开时才渲染 children。

官方示例(demo/lazyload.md)与测试用例('render lazyload correctly')验证了行为:初始只有 defaultActiveKey 指定的 1 个面板渲染内容(.arco-collapse-item-content 数量为 1),点击展开第二个面板后内容节点数量变为 2。

5.2 destroyOnHide(隐藏时销毁)

  • 默认值:-(不设置)
  • 作用:设置为 true 时,面板在折叠动画结束后被卸载(unmount),重新展开时再重建。适合内容重、需要彻底释放 DOM 与状态的场景。

注意两者的区别:lazyload 控制的是「首次是否渲染」——它不会在折叠后销毁已渲染的内容;destroyOnHide 控制的是「折叠后是否销毁」,与 lazyload 是不同维度的策略。

5.3 源码中的渲染决策

在 item.tsx 中,面板内容区由 react-transition-group 的 Transition 组件驱动,其挂载/卸载决策为:

mountOnEnter={'destroyOnHide' in props ? destroyOnHide : ctx.destroyOnHide || ctx.lazyload}
unmountOnExit={'destroyOnHide' in props ? destroyOnHide : ctx.destroyOnHide}
  • mountOnEnter:只有进入(展开)动画时才挂载内容。判断优先级是「面板自身 destroyOnHide → 容器 destroyOnHide → 容器 lazyload」,三者任一为真即延迟挂载;
  • unmountOnExit:退出(折叠)动画完成后是否卸载。只考虑 destroyOnHide(面板自身优先,其次容器),lazyload 不会导致卸载。

展开/收起的高度动画(onEnter、onEntering、onExiting 等回调)通过逐帧操作 divRef.current.style.height 实现:从 0 → scrollHeight → auto,收起时反向 offsetHeight → 0,配合 style/index.less 中 transition: height 的 0.2s 过渡完成平滑动画。此外,changelog/index.zh-CN.md 中提到,2.66.1 版本修复了「内容异步加载时已展开面板展示不完整」的问题,说明该过渡机制对异步内容的兼容性也在持续改进。

5.4 面板级优先级

Collapse.Item 上也可以单独设置 destroyOnHide,官方文档明确其优先级高于 Collapse.destroyOnHide。从 item.tsx 源码可以看到,只要面板自身 props 中存在 destroyOnHide(即 'destroyOnHide' in props),无论父级怎么设置,都以面板自身的值为准。这在「绝大多数面板保留内容、个别重面板需要销毁」的混合场景下非常实用。

六、触发区域:triggerRegion

triggerRegion 是 2.41.0 版本新增的属性(changelog/index.zh-CN.md),用于精确控制「点击哪些区域可以触发折叠操作」,可选值为 'header' 或 'icon'。

  • 默认行为(不设置):整个头部(header)都可以点击切换;
  • triggerRegion="header":仅点击标题文字区域(.arco-collapse-item-header-title)触发折叠,点击图标不触发;
  • triggerRegion="icon":仅点击展开图标触发,点击标题文字不触发。

官方示例(demo/triggerRegion.md)演示了 triggerRegion="header" 的用法。

这个属性的实现非常精妙,见 item.tsx 的 clickEventHandler。组件把头部点击拆分为三个层级:

  • regionLevel = 0:图标区域(IconHover 的 onClick);
  • regionLevel = 1:标题文字区域(-header-title 的 onClick);
  • regionLevel = 2:整个头部容器(-header 的 onClick)。

判断逻辑为:

const triggerRegionLevel = triggerRegion === 'icon' ? 0 : triggerRegion === 'header' ? 1 : 2;
if (
  regionLevel === triggerRegionLevel ||
  // 当 triggerRegion 设置为 header 时,点击图标同样会触发 onChange
  (triggerRegion === 'header' && [0, 1].includes(regionLevel))
) {
  ctx.onToggle(name, e);
}

注意其中的特殊规则:当 triggerRegion="header" 时,点击图标也会触发切换(因为图标属于头部交互的一部分),而点击整个头部容器(level 2)不会触发。测试用例('expand and collapse correctly with trigger region')完整验证了这一行为:点击整个 header 不展开,点击 -header-title 展开,再点击图标折叠。

样式层面(style/index.less),设置了 data-active-region 属性后,容器自身取消手型光标,只有可触发区域的子元素恢复 cursor: pointer,给用户清晰的交互提示。

七、面板内容与头部定制

7.1 header:自定义头部

Collapse.Item.header 接受任意 ReactNode,允许在头部嵌入图标、标签等富内容。测试用例('render self header correctly')展示了在 header 中组合 IconInfoCircle 的写法。注意:header 渲染在 .arco-collapse-item-header-title 容器内,点击它会触发折叠(除非设置了 triggerRegion)。

7.2 extra:右侧额外节点

extra 用于在面板头部最右侧渲染额外内容(如操作图标、按钮),示例见 demo/extra.md:

<CollapseItem
  header="Beijing Toutiao Technology Co., Ltd."
  name="1"
  extra={<IconMoreVertical />}
>
  ...
</CollapseItem>

实现细节(item.tsx):extra 渲染在 .arco-collapse-item-header-extra 容器中,且其 onClick 调用了 e.stopPropagation()——点击 extra 不会触发折叠,保证头部操作按钮与折叠行为互不干扰。样式上它使用 float: right 靠右对齐(style/index.less)。

7.3 style / contentStyle / className

  • Collapse.style:作用于整个 Collapse 容器(外层 div,见 collapse.tsx);
  • Collapse.Item.style:作用于单个面板的最外层 div(item.tsx);
  • Collapse.Item.contentStyle:2.15.0 版本新增,作用于内容区域的内层盒子 .arco-collapse-item-content-box(item.tsx),常用于调整内容内边距、字号等;
  • className(Collapse 与 Collapse.Item 均支持):追加自定义类名,类型为 string | string[]。

官方示例(demo/customStyle.md)演示了通过 style 给每个面板设置 borderRadius、marginBottom、overflow: hidden 等样式实现卡片化外观:

const customStyle = {
  borderRadius: 2,
  marginBottom: 24,
  border: 'none',
  overflow: 'hidden',
};
<CollapseItem style={customStyle} header="..." name="1">...</CollapseItem>

7.4 disabled:禁用面板

Collapse.Item.disabled 为 true 时该面板不可折叠。从 item.tsx 的 clickEventHandler 可见,disabled 会直接中断点击处理;同时头部会加上 -header-disabled 类名,样式层(style/index.less)将其光标改为 not-allowed,文字与图标使用禁用色 @collapse-title-color-text_disabled。键盘上该面板的 tabIndex 也会被设为 -1(item.tsx),使其不可聚焦。

八、边框与简洁样式

Collapse.bordered 控制容器是否渲染边框,默认 true。设置为 false 时可获得无边框的简洁外观(官方示例 demo/borderless.md):

<Collapse bordered={false} defaultActiveKey={['1']} style={{ maxWidth: 1180 }}>
  ...
</Collapse>

从实现看(collapse.tsx),容器类名会据此切换为 arco-collapse-border 或 arco-collapse-borderless;样式层(style/index.less)中 -borderless 仅去除容器外边框,面板之间的分隔线仍然保留,因此多个面板依然层次清晰。注意 bordered 的默认值在 collapse.tsx 的 defaultProps 中声明,并可通过 ConfigProvider 的 componentConfig.Collapse 全局覆盖。

九、样式定制与主题变量

Collapse 的样式完全基于 Arco Design 的设计令牌(token)构建,定义在 style/token.less。如果你想深度定制外观,可以覆盖这些 Less 变量:

变量 默认值 作用
@collapse-border-width / @collapse-border-radius @border-1 / @radius-medium 容器边框宽度与圆角
@collapse-color-border var(--color-neutral-3) 容器边框颜色
@collapse-title-font-size @font-size-body-3 头部字号
@collapse-title-padding-horizontal 13px 头部水平内边距
@collapse-title-color-text / @collapse-title-color-text_disabled color-text-1 / color-text-4 头部文字色(含禁用态)
@collapse-title-font-weight_active @font-weight-500 展开面板头部字重
@collapse-content-color-bg color-fill-1 内容区背景色
@collapse-content-padding-vertical @spacing-4 内容区垂直内边距
@collapse-expand-icon-size 14px 展开图标尺寸
@collapse-color-expand-icon color-neutral-7 展开图标颜色
@collapse-item-color-border color-neutral-3 面板分隔线颜色
@collapse-color-box-shadow color-primary-light-3 键盘聚焦(:focus-visible)光环颜色

此外,展开面板的头部会应用 @collapse-title-color-bg_active 背景与 @collapse-title-color-border 边框(style/index.less),让激活态一目了然。由于所有颜色都基于 CSS 变量(var(--color-*)),跟随 Arco 主题切换(亮/暗/自定义)时会自动适配。

十、无障碍与键盘操作

Collapse 在无障碍方面做得比较完整,相关实现分散在 item.tsx 中:

  • 头部容器带有 role="button"、aria-disabled(禁用面板为 true)与 aria-expanded(当前展开状态)等 ARIA 属性(item.tsx);
  • 内容区带有 role="region",便于读屏软件识别可展开区域;
  • 头部 tabIndex 在非禁用时为 0,可通过 Tab 聚焦;
  • 聚焦状态下按 Enter 键即可切换面板展开状态——这是 2.39.0 版本加入的能力(见 changelog/index.zh-CN.md),实现上通过 useKeyboardEvent 的 onPressEnter 调用 onToggle(item.tsx);
  • 聚焦时显示 :focus-visible 内阴影光环(@collapse-color-box-shadow)。

十一、API 汇总

Collapse 属性

参数 说明 类型 默认值 版本
accordion 是否手风琴模式(同时只展开一个面板) boolean - -
bordered 是否渲染边框 boolean true -
destroyOnHide 为 true 时,面板折叠后被卸载(unmount) boolean - -
lazyload 为 true 时,挂载阶段不渲染被隐藏的面板 boolean true -
expandIconPosition 展开图标位置 'left' | 'right' left -
triggerRegion 可触发折叠操作的区域 'header' | 'icon' - 2.41.0
expandIcon 自定义展开图标 ReactNode - -
activeKey 当前激活面板的 key(受控) string | string[] - -
defaultActiveKey 默认激活面板的 key(非受控初始值) string | string[] - -
className 节点类名 string | string[] - -
style 节点样式 CSSProperties - -
onChange 激活面板改变时触发 (key: string, keys: string[], e) => void - -

Collapse.Item 属性

参数 说明 类型 默认值 版本
destroyOnHide 面板折叠时是否销毁节点,优先级高于 Collapse.destroyOnHide boolean - -
disabled 是否禁用该面板 boolean - -
showExpandIcon 是否展示展开图标 boolean true -
name 面板唯一标识,对应 activeKey(必填) string - -
expandIcon 自定义展开图标 ReactNode - -
extra 头部右侧额外节点 ReactNode - -
header 头部内容,允许自定义 ReactNode - -
className 节点类名 string | string[] - -
contentStyle 内容区域附加样式 CSSProperties - 2.15.0
style 节点样式 CSSProperties - -

十二、常见问题与最佳实践

  1. onChange 触发次数问题:2.44.1 版本修复了「点击 icon 或 title 时 onChange 被触发 2 次」的 bug;当前版本下,每次展开/折叠操作只触发一次 onChange(测试用例 'onChange should be called only once on expanded or collapsed' 覆盖了该行为)。如果发现点击事件重复,请先确认升级版本。

  2. 事件冒泡:点击头部会正常向父节点冒泡(相关修复见 2.51.0 changelog 与测试用例 'onClick should propagate to parent nodes'),而点击 extra 会阻止冒泡,这是设计预期,可用于「头部可折叠 + extra 内按钮独立操作」的组合。

  3. React 18 / 19 兼容性:changelog 显示 Collapse 分别针对 React 18 严格模式(2.43.0)与 React 19(2.66.0)修复过报错问题,使用较新版本即可获得兼容保障。

  4. 性能取舍建议:

    • 面板内容简单、数量少:保持默认 lazyload 即可,无需额外配置;
    • 面板内容重(如长表格、复杂表单):可叠加 destroyOnHide,在折叠时彻底释放 DOM;
    • 需要「首次展开才渲染」但「展开后保留状态」:只用 lazyload,不要开 destroyOnHide,否则再次折叠会丢失面板内部状态。
  5. 全局统一配置:Collapse 的默认值(bordered、lazyload、expandIconPosition)声明在 collapse.tsx 的 defaultProps 中,且通过 useMergeProps 支持 ConfigProvider 的 componentConfig.Collapse 全局覆盖(collapse.tsx),项目级统一风格无需逐个组件传参。

结语

Arco Design 的 Collapse 组件在简洁的 API 表面之下,封装了展开动画、渲染时机管理、触发区域精细化控制、键盘无障碍等多层能力。无论是开箱即用的基础折叠、手风琴、嵌套面板,还是面向复杂业务场景的 lazyload / destroyOnHide / triggerRegion 组合策略,你都可以在 components/Collapse 目录下的 示例、源码 与 测试用例 中找到可直接参考的实现与验证。掌握本文的配置项语义与底层行为,你就能在项目中灵活运用并深度定制这个数据展示利器。

登录后查看全文
arco-design