Arco Design 折叠面板 Collapse 完整实战指南:API 详解、手风琴模式与源码实现原理

原创2026-10-12 14:57:051,959 阅读
文章标签:UI组件前端设计系统

Arco Design 折叠面板 Collapse 完整实战指南:API 详解、手风琴模式与源码实现原理

折叠面板(Collapse)是 Arco Design React 组件库中用于收纳复杂内容区域的经典数据展示组件,支持面板的展开 / 折叠、手风琴模式、懒加载渲染、隐藏时销毁以及自定义触发区域等能力。本指南以 components/Collapse/README.zh-CN.md 的官方 API 为骨架,结合组件源码 collapse.tsx、item.tsx 与全部 10 个官方示例(components/Collapse/demo),系统讲解 Collapse 的完整参数、典型用法与底层实现原理。读完本指南,你将掌握 Collapse 从基础使用到受控管理、手风琴、懒加载、销毁策略、触发区域定制等全部实战技能,并能理解其动画与事件机制。

Collapse 组件简介

Collapse 用于将复杂的内容区域分组和隐藏,通过点击面板头部实现折叠 / 展开。在 Arco Design 中,它由两个子组件组合而成:

  • Collapse:外层容器,管理面板的整体展开状态与公共配置;
  • Collapse.Item:单个面板项,通过 name 与 activeKey 建立唯一关联。

两个组件均从包入口导出,使用方式如下:

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

从源码角度看,Collapse 组件通过 CollapseComponent.Item = CollapseItem(见 collapse.tsx)把子组件静态挂载到自身,因此既可以使用 Collapse.Item 写法,也可以直接从包入口导入 CollapseItem。

基础用法:内容分组与展开

官方基础示例(demo/basic.md)展示了 Collapse 最典型的场景——将大段内容折叠收纳,默认展开多个面板:

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 是 Collapse.Item 的必填唯一标识,对应 Collapse 的 activeKey / defaultActiveKey;
  • defaultActiveKey={['1', '2']} 表示默认展开 name 为 1 和 2 的两个面板;
  • disabled 面板不可点击展开 / 折叠;
  • 默认允许同时展开多个面板。

测试用例(components/Collapse/test/index.test.tsx)验证了基础渲染:传入 defaultActiveKey="1" 时,第二个面板节点会包含 arco-collapse-item-active 类名,表明展开状态通过 CSS 类与内容区共同表达。

API 详解

Collapse 的完整 API 定义在 interface.ts,以下参数表与官方文档一致。

Collapse 参数

参数名 描述 类型 默认值 版本
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 - -

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 - -

源码中的 defaultProps(collapse.tsx)给出了三个默认值:bordered: true、lazyload: true、expandIconPosition: 'left',与文档表格一致。此外,组件还通过 useMergeProps 支持从 ConfigProvider 的 componentConfig?.Collapse 读取全局组件配置,这意味着你可以在应用层面统一预设 Collapse 的默认参数。

受控与非受控模式

activeKey 与 defaultActiveKey 构成了组件受控 / 非受控的双重入口:

  • 传入 defaultActiveKey 且不传 activeKey 时,组件内部自行维护展开状态(非受控);
  • 传入 activeKey 时,展开状态完全由外部控制(受控),此时必须配合 onChange 更新 activeKey 才能完成交互。

其实现位于 collapse.tsx 的 useMergeValue Hook:当 'activeKey' in props 时取外部受控值,否则使用内部状态;当 'defaultActiveKey' in props 时作为默认值初始化。onItemClick 中通过 if (!('activeKey' in props)) { setActiveKeys(newActiveKeys); } 判断是否写入内部状态(见 collapse.tsx),受控模式下仅回调 onChange 而不直接改内部值。

onChange 回调签名 (key: string, keys: string[], e) 中,key 为当前被点击的面板 name,keys 为更新后的完整展开面板数组,e 为原始事件对象。

手风琴模式:同时只展开一个面板

通过 accordion 属性开启手风琴模式(demo/accordion.md):

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

const App = () => {
  return (
    <Collapse accordion style={{ maxWidth: 1180 }}>
      <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="1">...</CollapseItem>
      <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="2">...</CollapseItem>
      <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="3">...</CollapseItem>
    </Collapse>
  );
};

源码中有两个层面的手风琴约束:

  1. 状态初始化时,getActiveKeys 函数对传入的 keys 执行 res.slice(0, 1),只保留第一个(collapse.tsx);
  2. 点击切换时,onItemClick 中 else if (accordion) { newActiveKeys = <a href="https://link.gitcode.com/i/0ca6a24b1aba2b591f132e19015bc8de" target="_blank">key]; } 直接替换为只包含当前面板的数组([collapse.tsx)。

测试用例(components/Collapse/test/index.test.tsx)连续点击 3 个面板后断言 activeKey 长度为 1,且值为最后点击的面板,验证了手风琴互斥行为。

嵌套面板

Collapse 支持面板内容中再次嵌套 Collapse(demo/inline.md):

<Collapse defaultActiveKey={['1', '2']} style={{ maxWidth: 1180 }}>
  <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="1">
    <Collapse defaultActiveKey={'1.1'}>
      <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="1.1">...</CollapseItem>
      <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="1.2">...</CollapseItem>
    </Collapse>
  </CollapseItem>
  <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="2">...</CollapseItem>
</Collapse>

嵌套时注意 name 应使用不同的命名空间(示例中用 '1.1'、'1.2' 区分层级),且每个 Collapse 是独立的 Context 与状态实例(通过 CollapseContext.Provider 隔离),不会互相干扰(collapse.tsx)。

简洁面板:无边框样式

设置 bordered={false} 可去除外层边框,获得更简洁的视觉样式(demo/borderless.md):

<Collapse bordered={false} defaultActiveKey={['1']} style={{ maxWidth: 1180 }}>
  {/* ...CollapseItem 子节点 */}
</Collapse>

实现上,collapse.tsx 根据 bordered 拼接 arco-collapse-border 或 arco-collapse-borderless 类名(collapse.tsx),样式文件中 &.@{collapse-prefix-cls}-borderless { border: none; }(style/index.less)移除容器边框,面板之间的分隔线依旧保留。

自定义面板样式

通过 style 可自定义单个面板的外观,例如实现卡片式圆角面板(demo/customStyle.md):

const customStyle = {
  borderRadius: 2,
  marginBottom: 24,
  border: 'none',
  overflow: 'hidden',
};

<Collapse bordered={false} defaultActiveKey={['1', '2']} style={{ maxWidth: 1180 }}>
  <CollapseItem style={customStyle} header="Beijing Toutiao Technology Co., Ltd." name="1">
    ...
  </CollapseItem>
  ...
</Collapse>

除 style 外,Collapse.Item 还提供 contentStyle(2.15.0 新增)用于单独定制内容区域样式。从源码看,contentStyle 被应用到内容区内部的 arco-collapse-item-content-box 容器上(item.tsx),而 style 作用于整个面板根节点,两者职责互补:style 管面板外壳,contentStyle 管展开内容内层。

展开图标:位置与自定义

expandIconPosition 控制图标位于头部左侧或右侧,官方示例通过 Radio.Group 动态切换(demo/iconPosition.md):

function App() {
  const [position, setPosition] = React.useState('left');
  return (
    <Collapse
      {...(position ? { expandIconPosition: position } : { expandIcon: null })}
      style={{ maxWidth: 1180 }}
    >
      <CollapseItem
        header="Beijing Toutiao Technology Co., Ltd."
        name="1"
        extra={<IconInfoCircle />}
      >
        ...
      </CollapseItem>
      ...
    </Collapse>
  );
}

关于图标的几个关键行为:

  • 默认图标与方向:expandIconPosition 为 left 时默认使用 IconCaretRight,为 right 时自动改用 IconCaretLeft(collapse.tsx),展开时图标旋转 90°(样式中的 -header-icon-down { transform: rotate(90deg); },见 style/index.less);
  • 自定义图标:Collapse 与 Collapse.Item 均可通过 expandIcon 传入任意 ReactNode,Item 级优先级更高;
  • 隐藏图标:expandIcon={null} 可整体隐藏图标,Collapse.Item 的 showExpandIcon={false} 则针对单个面板隐藏。测试用例(components/Collapse/test/index.test.tsx)验证了自定义 IconPlus 后 DOM 中出现 arco-icon-plus,且隐藏图表面板获得 arco-collapse-item-no-icon 类名。

额外节点:头部右侧扩展区

通过 extra 在面板头部最右侧渲染自定义节点,常用于放置操作按钮或图标(demo/extra.md):

import { IconMoreVertical } from '@arco-design/web-react/icon';

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

实现细节值得注意:extra 节点包裹在 arco-collapse-item-header-extra 中,其点击事件通过 e.stopPropagation() 阻止冒泡(item.tsx),因此点击 extra 区域不会误触发面板的展开 / 折叠,适合放置独立操作的按钮。

隐藏时销毁:destroyOnHide

destroyOnHide 用于在面板折叠时卸载内容节点(demo/destoryOnHide.md):

<Collapse destroyOnHide defaultActiveKey={['1', '2']} style={{ maxWidth: 1180 }}>
  ...
</Collapse>

该属性同时存在于 Collapse 和 Collapse.Item 两级,Item 级优先级更高。源码实现(item.tsx)体现在 Transition 组件的两个配置上:

mountOnEnter={'destroyOnHide' in props ? destroyOnHide : ctx.destroyOnHide || ctx.lazyload}
unmountOnExit={'destroyOnHide' in props ? destroyOnHide : ctx.destroyOnHide}

即:Item 自身声明了 destroyOnHide 时以 Item 为准;否则回退到 Collapse 级的 destroyOnHide。启用后,折叠动画结束(unmountOnExit)内容即被卸载,适合内容体积大、不需要常驻 DOM 的场景;代价是重新展开时内容需要重新挂载渲染。

懒加载:lazyload

lazyload 控制首次挂载时是否渲染隐藏面板,官方示例(demo/lazyload.md):

<Collapse lazyload defaultActiveKey={['1', '2']} style={{ maxWidth: 1180 }}>
  ...
</Collapse>

注意 lazyload 的默认值是 true:初始不可见的面板内容不会在挂载时渲染,直到该面板第一次展开才渲染其 children。它通过 Transition 的 mountOnEnter 生效(见上文源码)。与 destroyOnHide 的区别:

  • lazyload:首次展开前不渲染,展开后内容保留在 DOM 中,之后折叠不再卸载;
  • destroyOnHide:每次折叠都会卸载,重新展开再次挂载。

测试用例(components/Collapse/test/index.test.tsx)验证了懒加载行为:初始仅 1 个 arco-collapse-item-content 节点(默认展开的面板),点击第二个面板后变为 2 个,说明未展开面板的内容确实延迟渲染。

指定折叠触发区域:triggerRegion

triggerRegion(2.41.0 新增)用于控制可触发折叠操作的区域,可选 'header'(仅点击标题文字触发)或 'icon'(仅点击图标触发)(demo/triggerRegion.md):

<Collapse
  style={{ maxWidth: 1180 }}
  defaultActiveKey={['1']}
  triggerRegion="header"
>
  <CollapseItem header="Beijing Toutiao Technology Co., Ltd." name="1">
    ...
  </CollapseItem>
</Collapse>

底层实现将点击区域划分为三个层级(item.tsx):

regionLevel 对应区域 说明
0 展开图标(IconHover 容器) 点击图标
1 标题文字(header-title) 点击标题
2 整个头部(header) 点击头部任意处

clickEventHandler 的判定逻辑为:

  • triggerRegion === 'icon':仅 level 0 触发;
  • triggerRegion === 'header':level 0 和 level 1 均触发(注释明确说明"设置 header 时点击图标也应触发 onChange");
  • 未设置:level 2,即整个头部任意位置触发。

测试用例(components/Collapse/test/index.test.tsx)覆盖了 triggerRegion="header" 的三种点击场景,精确断言了每次点击后 activeKey 的变化。

键盘可访问性

Collapse 面板头部支持键盘操作:源码中头部节点设置了 role="button"、tabIndex 与 aria-expanded、aria-disabled 等无障碍属性,并通过 useKeyboardEvent 的 onPressEnter 支持回车键切换展开状态(item.tsx)。该能力自 2.39.0 起支持,禁用面板的 tabIndex 为 -1 且不响应按键。

源码级原理:状态、动画与样式

展开 / 折叠动画

内容区域的展开与折叠动画基于 react-transition-group 的 Transition 组件实现(item.tsx),核心思路是操作 height 属性:

  • 进入动画:onEnter 先设 height: 0 并 display: block,onEntering 阶段将高度设为 scrollHeight,onEntered 后恢复为 auto;
  • 退出动画:onExit 先记录当前 offsetHeight 作为起始高度(并强制 reflow 以保证动画生效),onExiting 将高度过渡到 0,onExited 后设 display: none 并重置高度;
  • 通过 addEndListener 监听 transitionend 事件驱动过渡状态机。

样式层面对应 transition: height 0.2s 的时长配置(style/index.less),收起时利用 transition: border-color 0s ease 0.19s 延迟隐藏边框,保证收起动画结束前边框不被提前隐藏(见 style/index.less 的注释说明)。

RTL 支持

Collapse 通过 ConfigContext 读取 rtl 配置,开启 RTL 时容器添加 arco-collapse-rtl 类名并设置 direction: rtl(collapse.tsx;style/index.less),图标位置、内边距等随之镜像适配。

样式变量定制

组件的全部视觉参数通过 Less 变量集中定义在 style/token.less,包括边框(@collapse-border-width、@collapse-border-radius)、标题与内容区配色(@collapse-title-color-bg、@collapse-content-color-bg)、图标尺寸与悬停背景(@collapse-expand-icon-size、@collapse-expand-icon-size-bg)等。所有颜色均引用 Arco 的 CSS 变量体系(如 --color-neutral-3、--color-bg-2),因此可随主题变量一键切换明暗主题。

版本演进要点

根据 components/Collapse/changelog/index.zh-CN.md 的变更记录,Collapse 的关键能力演进如下:

  • 2.15.0:Collapse.Item 新增 contentStyle,支持快速配置内容区域样式;
  • 2.39.0:支持通过键盘事件切换当前面板选中;
  • 2.41.0:新增 triggerRegion 属性以自定义折叠触发区域;
  • 2.43.0:修复 React 18 严格模式下的报错;
  • 2.44.1:修复点击 header icon 或 header title 时触发两次 onChange 的 bug;
  • 2.51.0:修复父级节点 onClick 不冒泡的问题;
  • 2.66.0:修复 React 19 下的报错;
  • 2.66.1:修复内容异步加载时已展开面板展示不完整的问题。

这些修复与测试用例(如"onChange 只触发一次"的断言,见 components/Collapse/test/index.test.tsx)相互印证,展示了组件在事件处理细节上的持续打磨。

小结

Arco Design 的 Collapse 组件在简洁 API 之下提供了相当完整的能力矩阵:多面板 / 手风琴双模式、受控与默认值双重状态入口、懒加载与销毁两种 DOM 优化策略、图标位置与自定义、extra 扩展区、触发区域精细控制以及键盘无障碍支持。理解其"Context 传递状态 + Transition 驱动 height 动画"的源码结构(collapse.tsx、item.tsx),将帮助你在复杂业务中灵活组合这些能力,做出符合预期的高质量折叠交互。

登录后查看全文
arco-design