ant-design-mobile Collapse 折叠面板:属性详解、手风琴模式与动画实现原理

原创2026-09-22 17:40:311,767 阅读
文章标签:UI组件前端移动开发

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),事件对象为原生 React MouseEvent。

五、样式定制与主题适配

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)深入定制动画与渲染行为。

登录后查看全文
ant-design-mobile