Arco Design 折叠面板 Collapse 组件完全指南:API 详解、源码原理与实战用例
Arco Design 折叠面板 Collapse 组件完全指南:API 详解、源码原理与实战用例
折叠面板(Collapse)是 Arco Design React 组件库中用于「分组与隐藏复杂内容区域」的核心数据展示组件,支持多面板展开、手风琴模式、无边框简洁样式、懒加载与隐藏销毁等多种交互形态。本文基于仓库内 components/Collapse/README.zh-CN.md 官方文档,结合 collapse.tsx、item.tsx 源码实现与完整测试用例,系统讲解 Collapse 的全部 API、底层工作原理与可复制的实战代码,读完即可在业务中直接落地使用。
一、组件概览:Collapse 能解决什么问题
Collapse 用于把复杂的内容区域分组收纳:默认可以同时展开多个面板,也可以只展开某几个面板;折叠时内容区通过高度过渡动画平滑收起。在 Arco Design 中,它由两个部分组成:
Collapse:容器组件,统一管理展开状态(activeKey)、手风琴模式、图标、触发区域等配置;Collapse.Item:单个面板,通过必填的name与容器交互。
从 components/index.tsx 可以看到组件以 Collapse 命名导出,并且 Collapse.Item 是挂载在容器上的静态子组件(见 collapse.tsx 中 CollapseComponent.Item = CollapseItem),使用时无需单独引入。
典型使用形态如下:
import { Collapse } from '@arco-design/web-react';
const CollapseItem = Collapse.Item;
<Collapse defaultActiveKey={['1', '2']}>
<CollapseItem header="面板标题一" name="1">
面板内容一
</CollapseItem>
<CollapseItem header="面板标题二" name="2">
面板内容二
</CollapseItem>
</Collapse>
二、Collapse API 全量解析
Collapse 的全部参数定义在 components/Collapse/interface.ts,默认值在 collapse.tsx 中集中声明(bordered: true、lazyload: true、expandIconPosition: 'left')。
| 参数名 | 描述 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| accordion | 是否是手风琴模式 | boolean | - |
- |
| bordered | 无边框样式 | boolean | true |
- |
| destroyOnHide | 是否销毁被折叠的面板 | boolean | - |
- |
| lazyload | 设置为 true 时,挂载时不会渲染被隐藏的面板 |
boolean | true |
- |
| expandIconPosition | 展开图标的位置 | 'left' | 'right' |
left |
- |
| triggerRegion | 可触发折叠操作的区域 | 'header' | 'icon' |
- |
2.41.0 |
| expandIcon | 自定义展开图标 | ReactNode | - |
- |
| activeKey | 当前面板选中值 | string | string[] |
- |
- |
| className | 节点类名 | string | string[] |
- |
- |
| defaultActiveKey | 默认展开的面板 | string | string[] |
- |
- |
| style | 节点样式 | CSSProperties | - |
- |
| onChange | 展开面板改变时触发 | (key: string, keys: string[], e) => void |
- |
- |
其中几个关键参数的底层行为值得深入理解:
受控与非受控切换。 源码使用 useMergeValue 管理 activeKeys,onItemClick 中先复制当前 keys、移除或追加目标 key,随后通过 if (!('activeKey' in props)) 判断是否受控:只有未传入 activeKey 时才更新内部状态,最后无论受控与否都会回调 onChange(key, newActiveKeys, e)(见 collapse.tsx)。因此:
- 只传
defaultActiveKey:非受控,用户点击面板由组件内部维护展开状态; - 传
activeKey但不传onChange:受控但状态不更新,表现为点击无效,适合纯展示场景; - 传
activeKey且传onChange:完全受控,需在回调中自行更新activeKey。
手风琴模式。 accordion 为 true 时,内部通过 getActiveKeys 对 keys 做 slice(0, 1) 截断,展开新面板时直接替换为 <a href="https://link.gitcode.com/i/1b897d5a4beb7b2cfde3ad2bbf7f4207" target="_blank">key](见 [collapse.tsx),保证任意时刻最多只有一个面板处于展开状态。
默认展开图标。 未自定义 expandIcon 时,图标位置为 left 使用 IconCaretRight,为 right 时自动切换为 IconCaretLeft(见 collapse.tsx),省去了手动适配箭头方向的麻烦。
三、Collapse.Item API 全量解析
单个面板的参数定义在 components/Collapse/interface.ts,面板渲染逻辑在 item.tsx。
| 参数名 | 描述 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| 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 | - |
- |
name 是唯一标识且必填,它决定该面板与 activeKey / defaultActiveKey 的对应关系;面板是否展开由 ctx.activeKeys?.indexOf(name) > -1 判定(见 item.tsx)。
图标优先级。 expandIcon 的解析逻辑是:面板自身显式传入 expandIcon 时使用自身图标,否则回退到容器的 expandIcon;showExpandIcon={false} 时不渲染图标,同时容器会给面板追加 -no-icon 类名,自动收起图标占位的内边距(见 item.tsx 与 index.less)。
destroyOnHide 优先级。 源码通过 'destroyOnHide' in props 判断面板是否显式声明了该属性,声明则以面板自身为准,否则继承容器配置(见 item.tsx)。
extra 不参与折叠交互。 extra 渲染在头部最右侧,其点击事件通过 e.stopPropagation() 阻断冒泡(见 item.tsx),因此放置在 extra 中的操作按钮(如更多菜单、设置图标)不会误触发展开/折叠。
四、核心实战用例
仓库 components/Collapse/demo 目录提供了 10 个官方示例,覆盖了绝大多数业务形态,下面逐一给出可直接复制的代码。
4.1 基本用法:默认展开多个面板
不传任何状态参数时默认全部折叠;通过 defaultActiveKey 可指定初始展开的面板。header 支持任意 ReactNode,disabled 面板不可点击但仍可正常展示内容:
import { Collapse, Divider } from '@arco-design/web-react';
const CollapseItem = Collapse.Item;
<Collapse defaultActiveKey={['1', '2']} style={{ maxWidth: 1180 }}>
<CollapseItem header="面板标题一" name="1">
面板内容一
<Divider style={{ margin: '8px 0' }} />
面板内容二
</CollapseItem>
<CollapseItem header="禁用面板示例" name="2" disabled>
该面板无法被展开或折叠。
</CollapseItem>
</Collapse>
4.2 手风琴模式
设置 accordion 后,同一时刻仅允许展开一个面板,展开新面板会自动收起旧面板:
import { Collapse } from '@arco-design/web-react';
const CollapseItem = Collapse.Item;
<Collapse accordion style={{ maxWidth: 1180 }}>
<CollapseItem header="北京字节跳动科技有限公司" name="1">内容一</CollapseItem>
<CollapseItem header="公司介绍" name="2">内容二</CollapseItem>
<CollapseItem header="底层 AI 技术" name="3">内容三</CollapseItem>
</Collapse>
测试用例 expand and collapse correctly when accordion 验证了手风琴模式下连续点击三个面板后 activeKey 始终只有 1 个、且为最后点击的面板(见 components/Collapse/test/index.test.tsx)。
4.3 嵌套面板
Collapse 支持在面板内容中嵌套新的 Collapse,注意子级面板的 name 使用 '1.1'、'1.2' 这类点号命名即可与外层 '1' 天然隔离:
<Collapse defaultActiveKey={['1']}>
<CollapseItem header="外层面板" name="1">
<Collapse defaultActiveKey={'1.1'}>
<CollapseItem header="内层面板一" name="1.1">内层内容</CollapseItem>
<CollapseItem header="内层面板二" name="1.2">内层内容</CollapseItem>
</Collapse>
</CollapseItem>
</Collapse>
4.4 简洁无边框面板
传入 bordered={false} 去掉容器外边框,得到更轻盈的视觉样式;源码中容器类名会在 -border 与 -borderless 之间切换(见 collapse.tsx):
<Collapse bordered={false} defaultActiveKey={['1']} style={{ maxWidth: 1180 }}>
{/* ...CollapseItem 同上 */}
</Collapse>
4.5 自定义面板样式
通过 CollapseItem 的 style 可实现卡片化、留白化的自定义外观,配合 bordered={false} 效果更佳:
const customStyle = {
borderRadius: 2,
marginBottom: 24,
border: 'none',
overflow: 'hidden',
};
<Collapse bordered={false} defaultActiveKey={['1', '2']}>
<CollapseItem style={customStyle} header="自定义样式面板一" name="1">内容</CollapseItem>
<CollapseItem style={customStyle} header="自定义样式面板二" name="2">内容</CollapseItem>
</Collapse>
4.6 头部额外节点
extra 常配合图标用于在头部右侧放置操作入口(如 IconMoreVertical);因为 extra 的点击被阻断冒泡,点击它不会触发面板折叠:
import { IconMoreVertical } from '@arco-design/web-react/icon';
<Collapse defaultActiveKey={['1']}>
<CollapseItem
header="带操作菜单的面板"
name="1"
extra={<IconMoreVertical />}
>
面板内容
</CollapseItem>
</Collapse>
4.7 展开图标位置与隐藏
expandIconPosition 支持 left / right 两个位置;若需要彻底隐藏展开图标,可传 expandIcon={null}。以下示例用 Radio.Group 动态切换三种形态:
import React from 'react';
import { Collapse, Radio, Grid, Typography } from '@arco-design/web-react';
const [position, setPosition] = React.useState('left');
<Collapse
{...(position
? { expandIconPosition: position }
: { expandIcon: null })}
>
<CollapseItem header="面板一" name="1" extra={<IconInfoCircle />}>内容</CollapseItem>
</Collapse>
4.8 Lazyload:首次展开时才渲染内容
lazyload 默认值为 true。开启后,被隐藏面板的 children 在挂载阶段不会渲染,直到该面板第一次展开才渲染并保留。测试用例 render lazyload correctly 验证了:初始只有 defaultActiveKey=<a href="https://link.gitcode.com/i/984e6cd5994eb23fc335fef2c176b5c7" target="_blank">'0'] 对应的 1 个 .arco-collapse-item-content,点击第二个面板后变为 2 个(见 [components/Collapse/test/index.test.tsx)。这一机制对「面板内含重组件 / 大数据列表 / 图表」的场景非常有用,可显著降低首屏渲染成本:
<Collapse lazyload defaultActiveKey={['1']}>
<CollapseItem header="首次展开才渲染" name="1">内容</CollapseItem>
</Collapse>
4.9 DestroyOnHide:折叠时销毁节点
与 lazyload 不同,destroyOnHide 在面板折叠时卸载内容节点,重新展开时重新挂载。适合不希望保留面板内部状态(如表单输入、播放器进度)的场景:
<Collapse destroyOnHide defaultActiveKey={['1', '2']}>
<CollapseItem header="折叠即销毁的面板" name="1">内容</CollapseItem>
</Collapse>
两者的底层差异体现在 item.tsx 的 Transition 配置上:
mountOnEnter:destroyOnHide || lazyload决定——lazyload只在首次进入时挂载,destroyOnHide则每次折叠都会卸载;unmountOnExit:destroyOnHide决定折叠动画结束后是否真正卸载 DOM。
4.10 triggerRegion:精确控制触发区域(2.41.0 起)
triggerRegion 用于限制「哪些区域点击会触发展开/折叠」,取值 'header'(仅标题文字区域)或 'icon'(仅展开图标区域)。事件处理函数按区域分级:icon 为 0 级、标题为 1 级、头部容器为 2 级,命中规则见 item.tsx——特别地,当 triggerRegion="header" 时,点击图标同样会触发切换,避免用户困惑:
<Collapse defaultActiveKey={['1']} triggerRegion="header">
<CollapseItem header="仅标题可触发折叠" name="1">内容</CollapseItem>
</Collapse>
测试用例 expand and collapse correctly with trigger region 精确验证了该交互:点击 header 空白处不触发、点击标题文字触发展开、再次点击图标区域收起(见 components/Collapse/test/index.test.tsx)。
五、源码原理深度解析
5.1 状态管理:Context 驱动 + 受控合并
容器通过 CollapseContext.Provider 把 activeKeys、onToggle、expandIcon、expandIconPosition、triggerRegion、lazyload、destroyOnHide 下发给所有面板(见 collapse.tsx),面板通过 useContext(CollapseContext) 消费。整个组件不依赖额外状态管理库,props 经 useMergeProps 与 ConfigProvider 的 componentConfig.Collapse 合并,也就是说可以通过 ConfigProvider 为全局所有 Collapse 统一设置默认参数(对应测试 components/Collapse/test/index.test.tsx 中的 componentConfigTest)。
5.2 展开/折叠动画:基于 react-transition-group 的高度过渡
内容区的展开收起动画不是简单的显隐切换,而是精确的高度过渡(见 item.tsx):
onEnter:设置height: 0、display: block;onEntering:读取scrollHeight并设置为显式像素高度,触发过渡动画;onEntered:还原为height: auto,避免内容动态变化时高度受限;- 收起时
onExit先记录offsetHeight并强制 reflow,再过渡到height: 0,最后onExited设置display: none。
对应样式上,内容区 transition: height 0.2s、图标 transition: transform 0.2s,展开时图标 rotate(90deg) 旋转(见 components/Collapse/style/index.less)。
5.3 键盘可访问性与无障碍
头部区域是 role="button",带 aria-disabled、aria-expanded、tabIndex,并注册了 useKeyboardEvent 的 onPressEnter 监听,支持按回车键切换面板展开状态(见 item.tsx),这是 2.39.0 版本引入的能力(见 components/Collapse/changelog/index.zh-CN.md)。内容区使用 role="region"。此外容器还支持 rtl 模式,开启后自动添加 -rtl 类名并设置 direction: rtl。
5.4 样式 Token 与主题定制
Collapse 的样式全部由 components/Collapse/style/token.less 中的 Design Token 驱动,主要包括:
- 边框:
@collapse-border-width、@collapse-color-border(color-neutral-3)、@collapse-border-radius; - 标题:
@collapse-title-font-size、@collapse-title-color-text、@collapse-title-color-bg、展开态标题加粗@collapse-title-font-weight_active(500); - 内容:
@collapse-content-color-bg(color-fill-1)、@collapse-content-font-size; - 图标:
@collapse-expand-icon-size(14px)、@collapse-color-expand-icon(color-neutral-7); - 间距:
@collapse-title-padding-horizontal(13px)、@collapse-content-padding-vertical。
这些 token 都基于 var(--arco-*) CSS 变量实现,因此跟随 Arco Design 的全局主题(浅色/深色、品牌色)自动联动,也可通过 ConfigProvider 统一覆盖。
六、事件与受控行为细节
- onChange 签名:
(key: string, keys: string[], e) => void,key是本次发生变化的那个面板的 name,keys是切换后的完整展开 key 数组,e是原生事件对象。注意源码在受控场景下keys中已经包含本次切换的结果,直接setState即可。 - onChange 只触发一次:历史上存在「点击 icon 或 title 触发 2 次 onChange」的 bug,已在 2.44.1 修复,并有回归测试
onChange should be called only once on expanded or collapsed兜底(见 components/Collapse/test/index.test.tsx)。 - 事件冒泡:2.51.0 修复了父节点
onClick不冒泡的问题,测试onClick should propagate to parent nodes验证了点击面板头部后父容器监听器可正常收到事件。
七、版本演进与选型建议
从 components/Collapse/changelog/index.zh-CN.md 可以看到 Collapse 的演进脉络:
- 2.15.0:新增
Collapse.Item的contentStyle,快速定制内容区样式; - 2.39.0:支持键盘(回车)切换面板;
- 2.41.0:新增
triggerRegion,精确控制折叠触发区域; - 2.44.1:修复重复触发 onChange 的缺陷;
- 2.51.0:修复父级节点 onClick 冒泡;
- 2.66.x:修复 React 19 兼容性与异步内容展开不完整问题。
据此可以给出选型建议:若面板内容较重(图表、长列表),优先依赖默认开启的 lazyload;若内容包含表单等有状态组件且希望每次展开都是全新状态,使用 destroyOnHide;需要「一次只开一个」的导航式场景使用 accordion;交互上对点击区域有严格约束时使用 triggerRegion。
八、常见问题速查
- 面板点击不展开:检查是否传入了
activeKey但未在onChange中更新状态(受控模式未闭环);检查该面板name是否与activeKey一致;检查是否误设disabled。 - 默认所有面板都展开:
lazyload默认为true只影响渲染时机,不影响展开状态;确认defaultActiveKey是否拼写正确。 - extra 点击误触发折叠:仓库已通过
stopPropagation处理,若使用自定义渲染结构,请勿在 extra 内再嵌套可点击的 header 结构。 - 折叠动画异常:内容区高度依赖
scrollHeight计算,若内容在动画期间异步加载导致高度变化,参考 2.66.1 的修复——确保在展开完成后再注入异步内容。