Vant Collapse 折叠面板组件完全指南:从基础用法到源码级原理
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
Vant 的 Collapse 折叠面板用于将一组内容收纳进多个可折叠面板,点击面板标题即可展开或收起内容,是移动端"手风琴"交互(如常见问题、分组设置、筛选条件)的标准实现。本文以 Vant 官方文档为主体,结合仓库内组件源码(Collapse.tsx、CollapseItem.tsx)与测试用例,系统讲解 Collapse 的安装注册、全部 Props/Events/Methods/Slots、toggleAll 实例方法、主题定制变量,并深入剖析父子组件协调、展开动画与懒渲染等底层实现,帮助你在业务中快速落地并理解其工作机制。
安装与组件注册
Collapse 由两个组件组成:Collapse(容器)与CollapseItem(面板项),二者需同时注册。可通过app.use进行全局注册:
import { createApp } from 'vue'; import { Collapse, CollapseItem } from 'vant'; const app = createApp(); app.use(Collapse); app.use(CollapseItem);除全局注册外,Vant 还支持按需引入、unplugin-vue-components自动注册等更多方式,可参考文档 advanced-usage 中的组件注册章节。从源码结构看,collapse/index.ts 与 collapse-item/index.ts 通过withInstall包装组件,并声明了VanCollapse/VanCollapseItem的全局组件类型,注册后可直接在模板中使用<van-collapse>与<van-collapse-item>标签。
基础用法:用 v-model 控制展开状态
通过v-model绑定当前展开面板的name值。默认(非手风琴)模式下,v-model是一个数组,包含所有已展开面板的 name。
<van-collapse v-model="activeNames"> <van-collapse-item title="Title1" name="1"> The code is written for people to see and can be run on a machine. </van-collapse-item> <van-collapse-item title="Title2" name="2"> Technology is nothing more than the common soul of those who develop it. </van-collapse-item> <van-collapse-item title="Title3" name="3"> The frequency of people swearing during code reading is the only measure of code quality. </van-collapse-item> </van-collapse>import { ref } from 'vue'; export default { setup() { const activeNames = ref(['1']); return { activeNames }; }, };name用于唯一标识面板,可传number或string;当不传name时,默认取面板在当前 Collapse 中的索引(index),这一点在 CollapseItem.tsx 中通过props.name ?? index.value计算得出,demo 源码(demo/index.vue)也展示了ref([0])这种按索引控制的写法。
手风琴模式
开启accordion后,同一时间只允许展开一个面板。
<van-collapse v-model="activeName" accordion> <van-collapse-item title="Title1" name="1"> The code is written for people to see and can be run on a machine. </van-collapse-item> <van-collapse-item title="Title2" name="2"> Technology is nothing more than the common soul of those who develop it. </van-collapse-item> <van-collapse-item title="Title3" name="3"> The frequency of people swearing during code reading is the only measure of code quality. </van-collapse-item> </van-collapse>import { ref } from 'vue'; export default { setup() { const activeName = ref('1'); return { activeName }; }, };手风琴模式下v-model绑定的是单个number | string值,而不是数组。源码 Collapse.tsx 中的validateModelValue会进行校验:手风琴模式下v-model为数组、或非手风琴模式下不是数组时,都会在开发环境输出[Vant] Collapse前缀的错误提示。面板切换逻辑位于toggle方法(Collapse.tsx):
- 手风琴模式:点击已展开的面板会收起(
name === modelValue时置为''),点击其他面板则直接切换为当前 name; - 非手风琴模式:展开时向数组追加 name,收起时从数组过滤掉该 name,然后统一触发
change与update:modelValue事件。
测试 index.spec.tsx 验证了手风琴模式下连续点击的切换与收拢行为。
禁用面板
为CollapseItem设置disabled后,该面板不可点击展开/收起,标题会呈现禁用态样式。
<van-collapse v-model="activeNames"> <van-collapse-item title="Title1" name="1"> The code is written for people to see and can be run on a machine. </van-collapse-item> <van-collapse-item title="Title2" name="2" disabled> Technology is nothing more than the common soul of those who develop it. </van-collapse-item> <van-collapse-item title="Title3" name="3" disabled> The frequency of people swearing during code reading is the only measure of code quality. </van-collapse-item> </van-collapse>除disabled外,组件还提供readonly(只读)属性。二者在 CollapseItem.tsx 的onClickTitle中都被排除在点击响应之外;区别在于:readonly会额外把标题的isLink置为false并移除clickable样式(CollapseItem.tsx),即只读面板不显示可点击的箭头提示。测试 index.spec.tsx 专门验证了 readonly 面板点击后active不变、且标题不含van-cell--clickable类名。
自定义标题内容
CollapseItem内部标题基于 Vant 的Cell单元格组件实现(CollapseItem.tsx),因此可以使用title插槽完全自定义标题,也可以直接通过title、value、label、icon等属性快速组合标题区内容。
<van-collapse v-model="activeNames"> <van-collapse-item name="1"> <template #title> <div>Title1 <van-icon name="question-o" /></div> </template> The code is written for people to see and can be run on a machine. </van-collapse-item> <van-collapse-item title="Title2" name="2" icon="shop-o"> Technology is nothing more than the common soul of those who develop it. </van-collapse-item> </van-collapse>import { ref } from 'vue'; export default { setup() { const activeNames = ref(['1']); return { activeNames }; }, };CollapseItem 支持 6 个插槽:default(内容)、title(标题)、value(右侧文字)、label(标题下方描述)、icon(左侧图标)、right-icon(右侧箭头图标)。源码中通过CELL_SLOTS常量(CollapseItem.tsx)将插槽透传给内部Cell,测试 index.spec.tsx 对全部五个 Cell 插槽的渲染进行了快照验证。
全部展开 / 全部切换:toggleAll 方法
通过组件 ref 调用toggleAll实例方法,可以批量操作所有面板的展开状态。注意:toggleAll在手风琴模式下不可用。
<van-collapse v-model="activeNames" ref="collapse"> <van-collapse-item title="Title1" name="1"> The code is written for people to see and can be run on a machine. </van-collapse-item> <van-collapse-item title="Title2" name="2"> Technology is nothing more than the common soul of those who develop it. </van-collapse-item> <van-collapse-item title="Title3" name="3"> The frequency of people swearing during code reading is the only measure of code quality. </van-collapse-item> </van-collapse> <van-button type="primary" @click="openAll">Open All</van-button> <van-button type="primary" @click="toggleAll">Toggle All</van-button>import { ref } from 'vue'; export default { setup() { const activeNames = ref(['1']); const collapse = ref(null); const openAll = () => { collapse.value.toggleAll(true); }; const toggleAll = () => { collapse.value.toggleAll(); }; return { activeNames, openAll, toggleAll, collapse, }; }, };toggleAll支持布尔值与对象两种参数形式(TypeScript 类型为CollapseToggleAllOptions,定义见 Collapse.tsx):
import { ref } from 'vue'; import type { CollapseInstance } from 'vant'; const collapseRef = ref<CollapseInstance>(); // 全部切换(展开的收起,收起的展开) collapseRef.value?.toggleAll(); // 全部展开 collapseRef.value?.toggleAll(true); // 全部收起 collapseRef.value?.toggleAll(false); // 全部切换,跳过禁用面板 collapseRef.value?.toggleAll({ skipDisabled: true, }); // 全部展开,跳过禁用面板 collapseRef.value?.toggleAll({ expanded: true, skipDisabled: true, });toggleAll的底层实现在 Collapse.tsx:首先判断props.accordion,手风琴模式下直接return;随后把布尔参数归一化为{ expanded }对象,结合skipDisabled过滤出目标面板集合(禁用且跳过时保持其原有状态),最后汇总所有目标面板的name一次性更新 v-model。
此外,单个面板项也暴露了toggle实例方法(见下文 Types 小节),通过CollapseItemInstance可单独切换某个面板。
API 参考
Collapse Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| v-model | 当前展开面板的 name | 手风琴模式:number | string 非手风琴模式:(number | string)[] | - |
| accordion | 是否开启手风琴模式 | boolean | false |
| border | 是否显示外边框 | boolean | true |
源码中collapseProps的定义(Collapse.tsx)显示border使用truthProp(默认true)、accordion为布尔开关、modelValue接受String | Number | Array。当border为true时容器会挂载van-hairline--top-bottom细边框类名,测试 index.spec.tsx 验证了border: false时该边框类名不会出现。
Collapse Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| change | 切换面板时触发 | activeNames: string | number | Array<string | number> |
change与update:modelValue在updateName中一并触发(Collapse.tsx),前者用于业务监听,后者用于支持v-model双向绑定。
CollapseItem Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| name | 面板唯一标识 | number | string | index |
| icon | 左侧图标 | string | - |
| size | 标题大小,可设为large | string | - |
| title | 标题 | number | string | - |
| value | 右侧文字 | number | string | - |
| label | 标题下方的描述文字 | string | - |
| border | 是否显示内边框 | boolean | true |
| disabled | 是否禁用面板 | boolean | false |
| readonly | 是否只读(不显示箭头、不可点击) | boolean | false |
| is-link | 是否显示右侧链接箭头 | boolean | true |
| lazy-render | 是否在首次展开时才渲染内容 | boolean | true |
| title-class | 标题类名 | string | - |
| value-class | 右侧文字类名 | string | - |
| label-class | 描述文字类名 | string | - |
name的类型为numericProp,disabled/readonly为布尔开关,isLink与lazyRender均使用truthProp(默认true);其余title、value、label、icon、size及三个 class 属性来自cellSharedProps,与Cell组件共享(CollapseItem.tsx)。
Collapse Methods
通过 ref 获取 Collapse 实例并调用实例方法。
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| toggleAll | 切换所有面板的展开状态 | options?: boolean | object | - |
CollapseItem Methods
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| toggle | 切换面板展开状态 | expanded: boolean | - |
CollapseItem的toggle通过useExpose暴露(CollapseItem.tsx),默认在传入值与当前状态之间取反,最终调用父组件通过依赖注入下发的parent.toggle(name, newValue);测试 index.spec.tsx 分别验证了普通模式与手风琴模式下调用toggle()/toggle(false)对 active 值的影响。
Types
组件导出了以下 TypeScript 类型:
import type { CollapseProps, CollapseItemProps, CollapseItemInstance, CollapseToggleAllOptions, } from 'vant';CollapseItemInstance为组件实例类型,可用于调用单项toggle方法:
import { ref } from 'vue'; import type { CollapseItemInstance } from 'vant'; const collapseItemRef = ref<CollapseItemInstance>(); collapseItemRef.value?.toggle();这些类型分别定义于 Collapse.tsx(CollapseToggleAllOptions、CollapseProps、CollapseInstance)与 collapse-item/types.ts(CollapseItemExpose、CollapseItemInstance),并由各自 index.ts 统一导出。
CollapseItem Slots
| 插槽名 | 说明 |
|---|---|
| default | 面板内容 |
| title | 自定义标题 |
| value | 自定义右侧文字 |
| label | 自定义标题下方描述 |
| icon | 自定义左侧图标 |
| right-icon | 自定义右侧箭头图标 |
主题定制:CSS Variables
Collapse 组件对外暴露以下 CSS 变量,可通过 ConfigProvider 组件 或直接在:root上覆盖,实现全局或局部主题定制。这些变量的默认值定义在 collapse-item/index.less 中:
| 变量名 | 默认值 | 说明 |
|---|---|---|
| --van-collapse-item-duration | var(--van-duration-base) | 展开/收起动画时长 |
| --van-collapse-item-content-padding | var(--van-padding-sm) var(--van-padding-md) | 内容区内边距 |
| --van-collapse-item-content-font-size | var(--van-font-size-md) | 内容区字号 |
| --van-collapse-item-content-line-height | 1.5 | 内容区行高 |
| --van-collapse-item-content-text-color | var(--van-text-color-2) | 内容区文字颜色 |
| --van-collapse-item-content-background | var(--van-background-2) | 内容区背景色 |
| --van-collapse-item-title-disabled-color | var(--van-text-color-3) | 禁用标题文字颜色 |
除上述 7 个可直接覆盖的变量外,types.ts 还导出了CollapseItemThemeVars类型,便于在 TS 项目中获得主题变量名的类型提示。展开/收起动画基于height过渡实现:.van-collapse-item__wrapper设置overflow: hidden与transition: height var(--van-collapse-item-duration) ease-in-out,标题右侧箭头在展开态通过rotate(-90deg)旋转(collapse-item/index.less)。
源码级原理:父子协调、动画与懒渲染
Collapse 采用典型的"父组件通过依赖注入协调子组件"架构,理解这一机制有助于排查"面板状态不同步"类问题。
父子通信:Collapse在 Collapse.tsx 中定义注入 keyCOLLAPSE_KEY,通过@vant/use的useChildren收集所有CollapseItem子组件,并linkChildren下发toggle与isExpanded两个方法(Collapse.tsx);CollapseItem侧用useParent(COLLAPSE_KEY)获取父组件上下文,若脱离Collapse单独使用,开发环境下会输出<CollapseItem> must be a child component of <Collapse>的错误提示(CollapseItem.tsx)。每个面板的展开状态并不自持,而是实时通过parent.isExpanded(name)从父级 v-model 推导(CollapseItem.tsx),因此 v-model 是状态的唯一数据源。
展开动画:CollapseItem监听expanded变化后,先测量内容区offsetHeight,再利用raf/nextTick与doubleRaf分帧设置 wrapper 的height从0到实际高度(展开)或反向过渡(收起),动画结束后在onTransitionEnd中清理高度并隐藏内容(CollapseItem.tsx)。注释指出:展开用nextTick是为了避免 Safari 中打开时的闪烁,收起用raf是为了规避user-select: none场景下关闭动画失效的问题。
懒渲染:lazyRender默认开启,内容通过 use-lazy-render 包裹,只有面板首次展开(show.value为真)时才渲染默认插槽内容,未展开的面板不生成内容 DOM;测试 index.spec.tsx 验证了点击标题前.foo元素不存在、展开后才出现的完整流程。该特性在面板内容较重时能显著降低首屏渲染成本。
小结
Collapse 折叠面板覆盖了移动端最常见的"折叠-展开"交互场景:普通多面板模式与手风琴模式通过accordion一键切换,disabled/readonly满足权限控制需求,6 个插槽 + 继承自 Cell 的属性让标题区可以自由定制,toggleAll与单项toggle实例方法支持程序化批量操作,7 个 CSS 变量则让主题定制无需修改组件源码。结合本文对COLLAPSE_KEY依赖注入、高度过渡动画与懒渲染的实现分析,你不仅能在项目中熟练使用,也能在遇到交互异常时快速定位到 Collapse.tsx 与 CollapseItem.tsx 中的对应逻辑。
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考