Ant Design Vue Transfer 穿梭框组件完全指南:API、事件与源码级实现解析
【免费下载链接】ant-design-vue🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜项目地址: https://gitcode.com/gh_mirrors/an/ant-design-vue
Transfer 穿梭框是 Ant Design Vue 中用于在双栏之间移动元素的数据录入组件,左侧一栏为source(数据源),右侧一栏为target(已选目标),API 设计也完整对应这两个概念。本文以 components/transfer/index.zh-CN.md 官方文档为核心,结合仓库内的组件源码与演示用例,系统讲解 Transfer 的全部配置项、事件回调、Render Props 自定义能力,以及底层数据流与移动逻辑的实现细节。
何时使用 Transfer
穿梭框适合以下场景:
- 需要在多个可选项中进行多选时;
- 相比 Select 和 TreeSelect,穿梭框占据更大的空间,可以展示可选项的更多信息(例如标题、描述等富文本内容)。
其交互方式非常直观:选择一个或以上的选项后,点击对应的方向键,即可把选中的选项移动到另一栏。dataSource中的数据默认渲染在左边一栏,targetKeys中指定的 key 除外(即已移动到右侧的数据),这一分工从组件属性设计上就清晰可见。
核心 API 全解析
Transfer 的全部属性定义位于 components/transfer/index.tsx 的transferProps()中,以下表格完整覆盖官方文档并补充了源码中的默认值与实现细节:
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| dataSource | 数据源,其中的数据将会被渲染到左边一栏中,targetKeys中指定的除外。 | [{key: string.isRequired, title: string.isRequired, description: string, disabled: bool}][] | [] | |
| disabled | 是否禁用整个穿梭框 | boolean | false | |
| filterOption | 接收inputValue、option两个参数,当option符合筛选条件时返回true,反之返回false | (inputValue, option) => boolean | ||
| footer | 底部自定义内容,可设置为作用域插槽 | slot="footer"(作用域为{ direction }) | ||
| listStyle | 两个穿梭框的自定义样式 | CSSProperties | {} | |
| locale | 各种语言的文案配置 | object | { itemUnit: '项', itemsUnit: '项', notFoundContent: '列表为空', searchPlaceholder: '请输入搜索内容' } | |
| oneWay | 展示为单向样式(右侧条目可直接移除) | boolean | false | 3.0.0 |
| operations | 操作文案集合,顺序从上至下(第一项为“向右移动”按钮,第二项为“向左移动”按钮) | string[] | ['>', '<'] | |
| operationStyle | 操作栏的自定义样式 | CSSProperties | - | 3.0.0 |
| pagination | 使用分页样式,自定义渲染列表(children 插槽)下无效 | boolean \| { pageSize, simple, showSizeChanger?, showLessItems? } | false | 3.0.0 |
| render | 每行数据渲染函数,入参为dataSource中的项,返回 element;或返回普通对象,其中label字段为 element、value字段为 title | Function(record) \| slot | ||
| selectAllLabels | 自定义顶部多选框标题的集合 | VueNode \| ((info: { selectedCount, totalCount }) => VueNode) | - | 3.0.0 |
| selectedKeys(v-model) | 设置哪些项应该被选中 | string[] | [] | |
| showSearch | 是否显示搜索框 | boolean | false | |
| showSelectAll | 是否展示全选勾选框 | boolean | true | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 3.3.0 |
| targetKeys(v-model) | 显示在右侧框数据的 key 集合 | string[] | [] | |
| titles | 标题集合,顺序从左至右 | string[] | ['', ''] |
关键参数源码级补充
pagination的默认解析逻辑:在 components/transfer/ListBody.tsx 的parsePagination中,即使只传pagination(布尔值true),内部也会合并默认配置{ pageSize: 10, simple: true, showSizeChanger: false, showLessItems: false },因此默认每页 10 条、使用简洁分页样式;传入对象时按字段覆盖。rowKey的兜底机制:dataSource未带key时,可用rowKey函数动态生成,源码中通过watchEffect在渲染前执行record.key = rowKey(record)(见 index.tsx)。filterOption缺省行为:未提供时,list.tsx 中的matchFilter会退化为text.includes(inputValue),即对渲染后的文本做包含匹配;提供后则完全交由自定义函数决定。listStyle支持函数形式:可传(style: { direction }) => CSSProperties,源码通过handleListStyle在左右两栏分别调用,实现按方向差异化布局(见 index.tsx)。
事件回调
| 事件名称 | 说明 | 回调参数 | 版本 |
|---|---|---|---|
| change | 选项在两栏之间转移时的回调函数 | (targetKeys, direction, moveKeys): void | |
| scroll | 选项列表滚动时的回调函数 | (direction, event): void | |
| search | 搜索框内容改变时的回调函数 | (direction: 'left' \| 'right', value: string): void | - |
| selectChange | 选中项发生改变时的回调函数 | (sourceSelectedKeys, targetSelectedKeys): void |
事件与 v-model 的联动
从 index.tsx 源码可见,targetKeys与selectedKeys均通过v-model双绑(内部 emitupdate:targetKeys、update:selectedKeys):
- 当点击“向右”方向键时,
moveTo('right')将sourceSelectedKeys中未禁用的条目合并进targetKeys,随后依次触发update:targetKeys、change与selectChange,并清空左侧选中态(见moveTo实现,其中通过groupDisabledKeysMap过滤掉禁用项,保证 disabled 数据不可被移动)。 oneWay模式下,右侧列表允许直接移除条目:onRightItemRemove会从targetKeys中过滤掉指定 key 并触发change(..., 'left', ...),此时右侧列表的头部下拉菜单显示“移除全部/移除当前页”等操作(对应 list.tsx 中showRemove分支)。- 搜索框内容变化时,
handleFilter会 emitsearch事件,清空搜索时则 emitsearch(direction, '')。
Render Props:自定义渲染列表
Transfer 支持通过children插槽接收完整的自定义列表渲染能力,官方文档给出了作用域插槽可以拿到的参数集合:
{ "direction": String, "disabled": Boolean, "filteredItems": Array, "selectedKeys": Array, "onItemSelect": Function, "onItemSelectAll": Function }各参数说明如下:
| 参数 | 说明 | 类型 | 版本 |
|---|---|---|---|
| direction | 渲染列表的方向 | 'left' \| 'right' | |
| disabled | 是否禁用列表 | boolean | |
| filteredItems | 过滤后的数据 | TransferItem[] | |
| itemSelect | 勾选条目 | (key: string, selected: boolean) | |
| itemSelectAll | 勾选一组条目 | (keys: string[], selected: boolean) | |
| selectedKeys | 选中的条目 | string[] |
官方参考示例:
<a-transfer> <template #children="{ direction, filteredItems, selectedKeys, disabled: listDisabled, onItemSelectAll, onItemSelect, }" > <your-component /> <template> </a-transfer>从源码角度看,children插槽最终以renderList形式传入左右两个 List,并在 list.tsx 的renderListBody中渲染:只有当插槽返回了非空内容(filterEmpty(bodyContent).length > 0)时才视为自定义列表,否则回退到内置的ListBody默认列表。同时,一旦使用自定义 children,pagination将自动失效(mergedPagination = !children && pagination),这正是文档中“自定义渲染列表下无效”的底层原因。
搜索与过滤的完整链路
开启show-search后,Transfer 会在每栏顶部渲染搜索框(对应 search.tsx),过滤链路如下:
- 用户输入触发 List 内部的
handleFilter,先更新本地filterValue,再回调父级handleFilter以 emitsearch事件; - list.tsx 的
watchEffect会对dataSource逐条执行matchFilter(自定义filterOption或默认文本包含匹配),产出filteredItems与filteredRenderItems; - 过滤结果驱动列表渲染与“全选”状态计算:
checkStatus依据当前页可见项与选中项的关系返回'none' | 'part' | 'all',用于驱动表头复选框的半选态与全选态。
搜索框为空或过滤后无结果时,会显示notFoundContent(默认为 locale 中的“列表为空”,也支持通过#notFoundContent插槽覆盖)。
分页模式
大数据量场景下,直接给pagination传布尔值即可启用内置分页(参考 demo/pagination.vue 中 200 条数据的用例)。分页能力由 ListBody.tsx 实现:
- 通过
mergedPagination计算当前页数据(slice((current - 1) * pageSize, current * pageSize)); - 内部复用
Pagination组件,默认simple模式与size="small",pageSize、showSizeChanger、showLessItems均可通过对象形式覆盖; - 过滤后数据量变化时会自动校正当前页码,避免越界;
- 分页模式下,列表头部的全选下拉菜单会额外出现“选择当前页”“反选当前页”等基于
defaultListBodyRef.value.items(当前页条目)的批量操作。
注意:分页与children自定义列表互斥,且分页开启后表头不再显示默认全选勾选框,而是收敛为下拉菜单形式(checkAllCheckbox仅在非分页、非单向移除模式下渲染)。
双向绑定与移动逻辑的数据流
targetKeys是 Transfer 的状态核心,右侧列表的数据顺序严格跟随targetKeys的数组顺序。源码中watchEffect的处理逻辑如下(见 index.tsx):
- 通过
groupKeysMap(targetKeys)建立 key 到索引的映射; - 遍历
dataSource,命中targetKeys的记录按索引填入rightDataSource的对应位置,未命中的进入leftDataSource; - 因此左侧顺序与
dataSource一致,右侧顺序与targetKeys一致,移动操作(moveTo)通过concat与filter维持这一约定。
禁用条目在移动时会被groupDisabledKeysMap过滤,无法被移动;全选操作也会通过getEnabledItemKeys跳过disabled项,避免选中不可操作的数据。
重要注意事项:key 的唯一性
按照 Vue 最新的规范,组件数组应绑定 key。在 Transfer 中,dataSource里的数据必须指定key值,组件默认将每条数据的key属性作为唯一标识。如果你的数据没有该属性,务必使用rowKey指定数据列的主键:
// 比如你的数据主键是 uid return <Transfer :rowKey="record => record.uid" />;rowKey会在渲染前被同步执行并写入record.key,同时应用于左右两栏的数据分组与选中/移动逻辑,是自定义数据源下保证组件正确工作的关键配置。
实战示例汇总
仓库 components/transfer/demo 目录提供了完整的可运行用例,可与文档对照学习:
- 基本用法(basic.vue):展示
dataSource、targetKeys、selectedKeys双绑,以及render与change、selectChange、scroll回调的标准写法; - 带搜索框(search.vue):
show-search+ 自定义filterOption(按description匹配)+@search事件监听; - 单向样式(oneway.vue):
one-way模式下只能从 source 移到 target; - 高级用法(advanced.vue):自定义
operations文案、listStyle宽高、#footer与#notFoundContent插槽; - 自定义渲染行数据(custom-item.vue):通过
#render插槽渲染复杂行内容; - 自定义全选文字(custom-select-all-labels.vue):
selectAllLabels同时支持静态节点与函数形式(如({ selectedCount, totalCount }) => ...); - 分页(pagination.vue):200 条数据下直接使用
pagination布尔值启用分页。
组件的自动化测试覆盖于 components/transfer/tests目录(index.test.js、list.test.js、search.test.js),验证了禁用项不可移动、搜索过滤、全选/反选等核心行为;组件样式定义位于 components/transfer/style/index.tsx。类型定义(TransferItem、TransferDirection、SelectAllLabel、PaginationType等)可查阅 components/transfer/interface.ts 与 components/transfer/index.tsx。
结合本文的 API 表格、事件说明与源码级数据流解析,你可以完整掌握 Transfer 的受控状态管理、搜索过滤、分页、单向模式与自定义渲染,并将其灵活落地到实际的表单与数据挑选场景中。
【免费下载链接】ant-design-vue🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜项目地址: https://gitcode.com/gh_mirrors/an/ant-design-vue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考