ant-design-mobile Collapse 折叠面板:属性详解、手风琴模式与动画实现原理
ant-design-mobile Collapse 折叠面板:属性详解、手风琴模式与动画实现原理
导读
本文围绕 ant-design-mobile 的 Collapse(折叠面板)组件展开,讲解如何将复杂内容区域分组隐藏、如何使用手风琴模式,并深入源码分析展开/收起动画、受控/非受控状态管理与箭头图标的底层实现。读完本文,你将能够熟练配置 Collapse 与 Collapse.Panel 的全部 API,掌握 defaultActiveKey、activeKey、accordion 等属性的实战用法,并理解组件在渲染性能与动画细节上的设计取舍。
一、组件定位与适用场景
Collapse 是 ant-design-mobile 中用于"可以折叠/展开的内容区域"的容器组件,核心用途有两个(见 官方中文文档):
- 对复杂区域进行分组和隐藏,保持页面的整洁:例如将表单分区、设置项分组、FAQ 问答列表收纳进可折叠面板,减少页面首屏信息密度;
- 手风琴模式(Accordion):一种特殊的折叠面板,同一时刻只允许单个内容区域展开,适合"多选一"的场景,如课程目录、套餐选择等。
组件以组合式 API 暴露:Collapse 负责整体状态与列表布局,内部的每个面板通过 Collapse.Panel 声明,二者通过 attachPropertiesToComponent 挂载为静态属性(见 index.ts),因此用法与 antd 生态一致:
import { Collapse } from 'antd-mobile'
<Collapse>
<Collapse.Panel key='1' title='第一项'>面板内容</Collapse.Panel>
<Collapse.Panel key='2' title='第二项'>面板内容</Collapse.Panel>
</Collapse>
二、快速上手:基础用法与手风琴模式
仓库内置的 demo1.tsx 提供了两个最典型的场景。
2.1 基础用法:多面板独立展开
不传 accordion 时,多个面板可以同时展开。通过 defaultActiveKey 指定初始展开的面板:
<Collapse defaultActiveKey={['1']}>
<Collapse.Panel key='1' title='第一项'>
{content}
</Collapse.Panel>
<Collapse.Panel key='2' title='第二项'>
{content}
</Collapse.Panel>
<Collapse.Panel key='3' title='第三项'>
{content}
</Collapse.Panel>
</Collapse>
注意 defaultActiveKey 在非手风琴模式下类型为 string[](数组),这里 ['1'] 表示默认展开 key 为 '1' 的面板。
2.2 手风琴模式:同时只展开一个
添加 accordion 属性后切换为手风琴模式,此时 defaultActiveKey 的类型变为 string | null,且展开新面板会自动收起当前面板:
<Collapse accordion>
<Collapse.Panel key='1' title='第一项'>内容</Collapse.Panel>
<Collapse.Panel key='2' title='第二项'>内容</Collapse.Panel>
<Collapse.Panel key='3' title='第三项'>内容</Collapse.Panel>
</Collapse>
2.3 受控与完全关闭
从 collapse.tsx 的源码可以看到,手风琴模式下组件内部会把 activeKey 归一化为 string[] 处理(null 转换为空数组),因此:
- 手风琴模式下传
activeKey={null}可令所有面板收起; - 非手风琴模式下传
activeKey={[]}同样可令全部面板关闭; - 已经展开的面板再次点击标题会触发收起逻辑(
setActiveKey([])或从数组中过滤掉该 key)。
三、API 属性全解
3.1 Collapse 属性
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| accordion | 是否开启手风琴模式 | boolean |
false |
| activeKey | 当前展开面板的 key(受控) |
手风琴模式:string | null非手风琴模式: string[] |
- |
| arrowIcon | 自定义箭头图标,传入 ReactNode 时 antd-mobile 会自动增加旋转动画 | ReactNode | ((active: boolean) => React.ReactNode) |
- |
| defaultActiveKey | 默认展开面板的 key(非受控) |
手风琴模式:string | null非手风琴模式: string[] |
- |
| onChange | 切换面板时触发 | 手风琴模式:(activeKey: string | null) => void非手风琴模式: (activeKey: string[]) => void |
- |
3.2 Collapse.Panel 属性
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| arrowIcon | 自定义箭头图标 | ReactNode | ((active: boolean) => React.ReactNode) |
- |
| destroyOnClose | 不可见时是否销毁 DOM 结构 |
boolean |
false |
| disabled | 是否为禁用状态 | boolean |
false |
| forceRender | 被隐藏时是否渲染 DOM 结构 |
boolean |
false |
| key | 唯一标识符 | string |
- |
| onClick | 标题栏的点击事件 | (event: React.MouseEvent<Element, MouseEvent>) => void |
- |
| title | 标题栏左侧内容 | ReactNode |
- |
四、关键行为详解
4.1 渲染策略:forceRender / destroyOnClose
面板内容是否渲染由 useShouldRender 统一决定,规则如下:
forceRender为true:无论是否展开,始终渲染 DOM,适合需要提前挂载、测量尺寸或保持内部状态不丢失的场景;- 面板从未展开过且未设置
forceRender:不渲染 DOM(避免无谓开销),这一点被 collapse.test.tsx 的用例明确验证——初始状态下第二、三面板的data-testid查询结果为null; - 面板已展开过(已初始化):默认保留 DOM,仅通过
height: 0px隐藏,以复用渲染结果; destroyOnClose为true:关闭时销毁 DOM 结构,适用于内容较重、希望彻底释放内存的面板。
从 collapse.test.tsx 的手风琴用例可以看到,展开/收起本质上是控制内容容器高度的动画:收起面板样式为 height: 0px,而已渲染过的面板 DOM 仍然保留在文档中。
4.2 展开动画:基于 react-spring 的高度过渡
Collapse 的动画并非简单地切换 display,而是用 @react-spring/web 的 useSpring 对内容高度做物理弹簧动画(见 collapse.tsx):
- 通过
innerRef测量内容区真实高度inner.offsetHeight,再调用api.start({ height: inner.offsetHeight })展开、api.start({ height: 0 })收起; - 弹簧配置为
tension: 200, friction: 25, mass: 1, clamp: true,保证动画在到达目标值后立即钳制、不来回震荡; - 动画结束后高度值恢复为
'auto',避免固定像素高度影响响应式布局; - 使用 useIsomorphicUpdateLayoutEffect 保证在布局阶段同步测量,避免闪烁。
4.3 动态内容自适应
如果面板内容是异步加载的(如请求完成后才填充数据),内容高度会发生变化。源码通过 observe(MutationObserver 封装,见 use-mutation-effect.ts)监听内容区子节点变化,一旦高度改变立即重新执行高度动画,保证展开动画始终平滑。内置 demo2.tsx 的"动态内容"示例即演示了这种场景:内容先显示 DotLoading,1 秒后切换为 Result。
4.4 状态管理:受控与默认值
组件的展开状态基于 usePropsValue 实现:当传入 activeKey 时组件为受控模式,由外部完全接管状态;未传入时回退到 defaultActiveKey,内部自管理状态。手风琴模式在内部会被归一化为数组形态,并通过 mergedProps.onChange?.(v[0] ?? null) 将回调参数还原为 string | null(见 collapse.tsx),因此外部监听 onChange 时无需关心内部表示差异。
4.5 箭头图标:层级合并与自动旋转
箭头图标支持在 Collapse 与 Collapse.Panel 两层同时配置,通过 mergeProp 实现面板级优先于组件级的合并策略(见 collapse.tsx):
- 未配置时默认使用
antd-mobile-icons的DownOutline; - 传入
ReactNode(如自定义图标组件)时,antd-mobile 会为其包裹.adm-collapse-arrow容器,展开状态附加-active类,配合 collapse.less 中的transform: rotate(-180deg)与transition: all ease 0.3s实现自动旋转动画; - 传入函数
(active: boolean) => ReactNode时,组件不会套用旋转样式,图标完全由你根据active状态自行控制——demo2.tsx 中的加减号、对勾/叉号切换即是这种用法; - 从 collapse.tsx 的类型定义可以看到,
arrow属性已被标记为@deprecated,建议统一使用arrowIcon;测试用例 collapse.test.tsx 系统验证了两者混合配置时arrowIcon优先生效的合并规则。
4.6 disabled 与 onClick
disabled为true时面板不可点击、不可展开,但其余面板不受影响(见 collapse.test.tsx);onClick在点击标题栏时触发,先执行内部展开/收起逻辑,再调用面板自身的onClick(见 collapse.tsx),事件对象为原生 ReactMouseEvent。
五、样式定制与主题适配
Collapse 基于 List 组件构建,adm-collapse 为类名前缀,样式全部使用 CSS 变量驱动(见 collapse.less),可通过 antd-mobile 的全局主题变量定制:
- 面板标题区由
.adm-collapse-panel-header控制,内边距为12px 0; - 内容区使用
--adm-font-size-main控制字号、--adm-color-weak控制弱化文字颜色,与全局主题(theme-default.less)联动; - 展开动画相关旋转由
.adm-collapse-arrow-active提供,如需修改动画时长可覆盖该类的transition。
六、无障碍与测试保障
组件与测试体系完整对接无障碍检查:testA11y(<Collapse>label</Collapse>)(见 collapse.test.tsx)会执行 a11y 自动校验,保证折叠面板标题可作为可点击的可达元素。测试还覆盖了基础多选、手风琴单选、禁用态、箭头合并等核心交互,是理解组件契约的最佳参考。
七、小结
Collapse 以极简的 API 表面(Collapse + Collapse.Panel 两个组件、十余个属性)隐藏了高度动画、受控状态归一化、渲染策略与图标合并等复杂实现。掌握 accordion、activeKey / defaultActiveKey、forceRender / destroyOnClose 与 arrowIcon 的配合,即可在移动端页面中高效组织复杂内容,并在需要时通过源码(collapse.tsx)深入定制动画与渲染行为。