Naive UI Legacy Transfer 组件完整指南:双向穿梭选择器的 Props、过滤与虚拟滚动实战
Naive UI Legacy Transfer 组件完整指南双向穿梭选择器的 Props、过滤与虚拟滚动实战【免费下载链接】naive-uiA Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.项目地址: https://gitcode.com/gh_mirrors/na/naive-ui本指南以 Naive UI 仓库中legacy-transfer组件旧版穿梭框的官方文档 index.demo-entry.md 为主体结合其源码实现与单元测试系统讲解该组件的全部 Props、TransferOption数据结构、过滤、尺寸与虚拟滚动等核心能力并说明其已被新版 Transfer 取代的迁移背景。读完本文你将能够熟练使用n-legacy-transfer完成左右列表的数据穿梭并在超大数据量场景下正确开启虚拟滚动。组件定位与弃用声明n-legacy-transfer旧版穿梭框是一个典型的左、右、左、右双向列表选择组件用户在左侧Source列表中勾选选项点击按钮将其移动到右侧Target列表也可反向移回。仓库文档明确给出WarningThe transfer component is deprecated. It wont have any new feature and will be removed in the next major version. Its recommended to use new Transfer.这意味着该组件不再增加任何新功能将在下一个大版本中被移除官方推荐改用新版 Transfer 穿梭框组件。因此本文介绍的内容主要适用于维护存量代码、迁移老项目或阅读源码的场景新项目建议直接使用新版 Transfer。在源码层面组件注册名为LegacyTransfer对外导出为NLegacyTransfer其导出入口见 legacy-transfer/index.tsexport type { Option as LegacyTransferOption } from ./src/interface export { transferProps as legacyTransferProps, default as NLegacyTransfer } from ./src/Transfer基本用法与数据驱动模型与大多数受控组件一致n-legacy-transfer由options全部选项和value当前已选到右侧的值数组两个数据源驱动用户勾选并通过中间按钮移动后触发on-update:value更新值。参考官方示例 basic.demo.vue一个最基础的用法如下script langts setup import { ref } from vue function createOptions() { return Array.from({ length: 100 }).map((v, i) ({ label: Option ${i}, value: i, disabled: i % 5 0 })) } function createValues() { return Array.from({ length: 50 }).map((v, i) i) } const options createOptions() const value ref(createValues()) /script template n-legacy-transfer v-model:valuevalue :optionsoptions / /template要点说明v-model:value双向绑定当前已选值数组即右侧列表内容:options传入全部可选列表组件会依据value自动将选项切分为左侧未选与右侧已选两部分选项中的disabled: i % 5 0演示了按规则禁用部分选项。Transfer Props 完整 API 对照官方文档给出的 Props 表如下这是使用该组件的权威参考NameTypeDefaultDescriptiondefault-valueArraystring \| number \| nullnull默认值非受控模式下的初始值。disabledbooleantrue禁用状态。filterablebooleanfalse是否开启过滤筛选功能。filterfunction(pattern: string, option: TransferOption, from: source \| target) boolean默认是一个基础的 label 字符串匹配函数。optionsTransferOption[][]配置选项结构见下文 TransferOption Type。sizesmall \| medium \| largemedium尺寸。source-filter-placeholderstringundefined左侧Source搜索框占位文案。source-titlestringSource左侧列表标题。target-filter-placeholderstringundefined右侧Target搜索框占位文案。target-titlestringTarget右侧列表标题。valueArraystring \| number \| nullundefined手动设置时的当前值受控模式。on-update:value(value: Arraystring \| number) voidundefined值变化时的回调。virtual-scrollbooleanfalse是否启用虚拟滚动。受控与非受控模式从 use-transfer-data.ts 的源码可以看出组件同时支持受控与非受控两种模式const uncontrolledValueRef ref(props.defaultValue) const controlledValueRef toRef(props, value) const mergedValueRef useMergedState( controlledValueRef, uncontrolledValueRef )不传value、只传default-value时组件内部自行维护状态非受控传了value时组件完全由外部状态驱动受控并在用户操作时通过on-update:value通知外部更新。数据切分逻辑mergedValueRef即当前已选值会与options共同决定左右两侧列表const tgtValueSetRef computed(() new Set(mergedValueRef.value || [])) const srcOptsRef computed(() props.options.filter(option !tgtValueSetRef.value.has(option.value)) ) const tgtOptsRef computed(() { const optMap optMapRef.value return (mergedValueRef.value || []).map(v optMap.get(v)) })即右侧列表 按 value 顺序从 options 中取出已选中的选项左侧列表 全部选项减去已选中项。右侧列表的顺序由value数组的顺序决定而不是 options 中的原始顺序。移动按钮的状态机use-transfer-data.ts中通过两个useMemo控制中间按钮的可用性const fromButtonDisabledRef useMemo(() { if (mergedDisabledRef.value) return true return tgtCheckedValuesRef.value.length 0 }) const toButtonDisabledRef useMemo(() { if (mergedDisabledRef.value) return true return srcCheckedValuesRef.value.length 0 })也就是说左侧没有勾选任何选项时移到右侧按钮禁用右侧没有勾选任何选项时移回左侧按钮禁用。移动逻辑本身在 Transfer.tsx 中function handleToTgtClick(): void { doUpdateValue( srcCheckedValuesRef.value.concat(mergedValueRef.value || []) ) srcCheckedValuesRef.value [] } function handleToSrcClick(): void { const tgtCheckedValueSet new Set(tgtCheckedValuesRef.value) doUpdateValue( (mergedValueRef.value || []).filter(v !tgtCheckedValueSet.has(v)) ) tgtCheckedValuesRef.value [] }向右侧移动 左侧勾选值追加到当前值尾部移回左侧 从当前值中过滤掉右侧勾选值。关于 disabled 默认值的说明文档表格中disabled的默认值标注为true但 Transfer.tsx 中其 prop 声明为default: undefined随后通过useFormItem(props)与表单上下文合并得到mergedDisabledRef。因此实际禁用状态是组件自身 disabled 与所在表单/表单项禁用状态取并集的结果。若组件未放置于表单上下文中实际表现以disabled属性传入值为准。TransferOption Type选项数据结构n-legacy-transfer的选项类型在 interface.ts 中定义export type OptionValue string | number export interface Option { label: string value: OptionValue disabled?: boolean }官方文档对应的属性表PropertyTypeDescriptionlabelstring选项显示文本。valuestring \| number选项唯一值。disabledboolean选项禁用状态。三个字段的含义label直接决定列表项展示的文字也是默认filter的匹配对象value作为选项的唯一标识被用于Map索引optMapRef和Set去重必须保持唯一否则切分与移动逻辑会出错disabled为可选字段禁用的选项不可被勾选也不参与表头全选/半选的可用数量统计。过滤Filterable与自定义 filter开启filterable后左右两个列表顶部会各出现一个搜索框输入内容会即时过滤列表项。参考官方示例 filterable.demo.vuescript langts setup import { ref } from vue function createOptions() { return Array.from({ length: 100 }).map((v, i) ({ label: Option ${i}, value: i, disabled: i % 5 0 })) } function createValues() { return Array.from({ length: 50 }).map((v, i) i) } const options createOptions() const value ref(createValues()) /script template n-legacy-transfer v-model:valuevalue virtual-scroll :optionsoptions filterable / /template默认 filter 实现如果不传filter组件使用内置的大小写不敏感的子串匹配源码位于 Transfer.tsxfilter: { type: Function as PropTypeFilter, default: (pattern: string, option: Option) { if (!pattern) return true return ~${option.label} .toLowerCase() .indexOf(${pattern}.toLowerCase()) } }行为特征pattern为空字符串时放行所有选项否则将option.label与输入模式都转小写后做indexOf子串匹配匹配仅针对label字段不匹配value。自定义 filter 签名filter的类型为export type Filter ( pattern: string, option: Option, from: source | target ) boolean第三个参数from指示当前过滤发生在左侧source还是右侧target可用于实现两侧不同的过滤规则。过滤的计算逻辑在 use-transfer-data.tsconst filteredSrcOptsRef computed(() { if (!props.filterable) return srcOptsRef.value const { filter } props return srcOptsRef.value.filter(opt filter(srcPatternRef.value, opt, source) ) }) const filteredTgtOptsRef computed(() { if (!props.filterable) return tgtOptsRef.value const { filter } props return tgtOptsRef.value.filter(opt filter(tgtPatternRef.value, opt as Option, target) ) })两个列表的搜索关键词srcPatternRef/tgtPatternRef相互独立可分别过滤。自定义 filter 示例例如希望同时匹配 label 与 value并忽略大小写script langts setup import { ref } from vue import type { LegacyTransferOption } from naive-ui function customFilter( pattern: string, option: LegacyTransferOption, from: source | target ) { if (!pattern) return true const p pattern.toLowerCase() return ( option.label.toLowerCase().includes(p) || String(option.value).toLowerCase().includes(p) ) } const options refLegacyTransferOption[]([]) const value refArraystring | number([]) /script template n-legacy-transfer v-model:valuevalue :optionsoptions filterable :filtercustomFilter / /template过滤下的全选状态过滤开启时表头的全选仅针对**当前过滤结果中可用未禁用**的选项生效这一点由 use-transfer-data.ts 中的avlSrcValueSetRef/avlTgtValueSetRef与srcCheckedStatusRef/tgtCheckedStatusRef保证——它们基于filteredSrcOpts/filteredTgtOpts计算并区分checked全选、indeterminate半选与disabled无可选项三种状态。尺寸Size与占位文案定制size支持small | medium | large三档默认medium。参考官方示例 size.demo.vuescript langts setup import { ref } from vue function createOptions() { return Array.from({ length: 100 }).map((v, i) ({ label: Option ${i}, value: i, disabled: i % 5 0 })) } function createValues() { return Array.from({ length: 50 }).map((v, i) i) } const options createOptions() const value ref(createValues()) /script template n-space vertical n-legacy-transfer v-model:valuevalue :optionsoptions sizesmall / n-legacy-transfer v-model:valuevalue :optionsoptions sizelarge / /n-space /template示例中同时渲染 small 与 large 两个实例并提示Mixing sizes does not look harmonious——同一界面内混用不同尺寸会显得不协调实际项目中应保持尺寸统一。尺寸的底层实现尺寸并非简单地切换 class而是驱动主题变量的切换。Transfer.tsx 中根据当前尺寸从主题中取对应变量并注入 CSS 变量const itemSizeRef computed(() { const { value: size } mergedSizeRef const { self: { [createKey(itemHeight, size)]: itemSize } } themeRef.value return depx(itemSize) })主题中为每种尺寸分别定义了fontSize与itemHeight见 styles/light.ts 等主题文件最终通过cssVars输出为--n-font-size、--n-item-height等 CSS 变量供 styles/index.cssr.ts 中的样式使用。标题与占位文案source-title/target-title分别定制左右列表标题默认值为Source/Targetsource-filter-placeholder/target-filter-placeholder定制两侧搜索框占位文字需同时开启filterable才可见。标题同样支持 i18n 默认值useLocale(LegacyTransfer)会从 locale 配置中读取sourceTitle/targetTitle各语言包见 locales/common 下的LegacyTransfer配置项因此不传source-title/target-title时会使用当前 locale 的本地化文案。大数据量与虚拟滚动virtual-scroll当选项数量庞大时普通渲染会显著拖慢穿梭操作。官方示例 large-data.demo.vue 直接构造了42000 个选项来演示该场景script langts setup import { ref } from vue function createOptions() { return Array.from({ length: 42000 }).map((v, i) ({ label: Option${i}, value: i, disabled: i % 5 0 })) } function createValues() { return Array.from({ length: 50 }).map((v, i) i) } const options createOptions() const value ref(createValues()) /script template n-legacy-transfer v-model:valuevalue :optionsoptions virtual-scroll / /template示例说明原文If you have tons of data, you may need to speed the transfer up! Setvirtual-scrollon transfer to use a blazing fast transfer (which turns the ridiculous animation off).数据量巨大时请开启virtual-scroll获得极快的穿梭体验同时关闭了繁琐的动画。虚拟滚动内部实现从 TransferList.tsx 可以看出virtual-scroll开启与否对应两套完全不同的渲染策略开启虚拟滚动渲染vueuc的VirtualList仅渲染可视区域内的行配合NScrollbar通过scrollContainer/scrollContent同步滚动条并用keyFieldvalue指定虚拟列表的 key 字段关闭虚拟滚动渲染普通div容器 TransitionGroup为列表项移动提供入场/离场动画。virtualScroll ? ( VirtualList refvlInstRef style{{ height: 100% }} class{${mergedClsPrefix}-legacy-transfer-list-content} items{this.options} itemSize{this.itemSize} showScrollbar{false} onResize{syncVLScroller} onScroll{syncVLScroller} keyFieldvalue ... /VirtualList ) : ( div class{${mergedClsPrefix}-legacy-transfer-list-content} TransitionGroup nameitem appear{this.isMounted} css{!this.isInputing} ... /TransitionGroup /div )这也解释了示例中which turns the ridiculous animation off虚拟滚动模式下用TransitionGroup的动画被移除换来了大数据量下的性能。空列表渲染当某侧列表为空时组件默认渲染NEmpty空状态若通过全局配置注入了Transfer.renderEmptyuseConfig的mergedComponentPropsRef.value?.Transfer?.renderEmpty则会优先使用自定义的空状态渲染函数。表单联动与事件n-legacy-transfer通过useFormItem(props)接入 Naive UI 表单体系见 Transfer.tsxconst formItem useFormItem(props) const { mergedSizeRef, mergedDisabledRef } formItemsize、disabled会与所在n-form-item的配置合并值变化时通过doUpdateValue触发nTriggerFormInput()与nTriggerFormChange()从而驱动表单的校验与提交逻辑。事件上首选使用on-update:value或v-model:value兼容on-update:value的onUpdateValue写法旧事件on-change仍被支持但在开发环境下会打印弃用警告提示改用on-update:value见源码if (props.onChange ! undefined) { warnOnce( legacy-transfer, on-change is deprecated, please use on-update:value instead. ) }单元测试覆盖情况组件仓库内的测试 Transfer.spec.ts 覆盖了以下行为可作为使用与排错参考按需引入mount(NLegacyTransfer)不报错disabled禁用时根节点带有n-legacy-transfer--disabledclassfilterable开启后根节点带有n-legacy-transfer--filterableclassfilter在filterable与自定义filter下向输入框输入内容onFilter会被调用sizesmall / medium / large三种尺寸的 style 均与快照匹配快照文件placeholdersource-filter-placeholder/target-filter-placeholder会分别写入两个搜索框的placeholder属性titlesource-title/target-title会渲染到对应列表头文本中。此外 server.spec.tsx 还验证了该组件在 SSR服务端渲染场景下的可用性。总结与迁移建议n-legacy-transfer提供了一套完整且经过测试的双向穿梭选择能力受控/非受控双模式、默认子串过滤与自定义 filter、三档尺寸、全选/半选状态机以及针对数万级数据量的虚拟滚动。其数据切分、过滤计算、按钮状态等核心逻辑集中在 use-transfer-data.ts渲染结构见 Transfer.tsx整体实现清晰、可读性高非常适合作为 Vue 3 复杂受控组件的学习范本。最后再次提醒该组件已进入弃用状态官方明确不会再增加新功能并将在下个主版本移除。对于存量项目建议规划向新版 Transfer 组件 的平滑迁移对于新项目请直接使用新版 Transfer。【免费下载链接】naive-uiA Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast.项目地址: https://gitcode.com/gh_mirrors/na/naive-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Python实现超透镜设计:从建模到电场分析全流程

Python实现超透镜设计:从建模到电场分析全流程

1. 超透镜设计入门:从参数化建模到电场分析超透镜设计看似高深,但通过代码实操可以快速掌握核心原理。我最近用Python实现了一套完整的超透镜设计流程,从参数化建模到相位计算再到电场分析,整个过程比想象中直观得多。关键在于理解…

2026/9/21 18:36:32 阅读更多 →
Packer SLSA 溯源 CI 参考工作流:provenance 后处理器与 GitHub Actions 无密钥签名实战

Packer SLSA 溯源 CI 参考工作流:provenance 后处理器与 GitHub Actions 无密钥签名实战

Packer SLSA 溯源 CI 参考工作流:provenance 后处理器与 GitHub Actions 无密钥签名实战 【免费下载链接】packer Packer is a tool for creating identical machine images for multiple platforms from a single source configuration. 项目地址: https://gitco…

2026/9/21 18:36:32 阅读更多 →
微信零钱免费转到卡里性能优化入门到精通

微信零钱免费转到卡里性能优化入门到精通

微信零钱免费转到卡里性能优化入门到精通 官方文档关于接口限流和并发处理的描述往往篇幅冗长,导致开发者在排查“微信零钱免费转到卡里”延迟高时抓不住重点。想要从入门到精通地解决这一性能瓶颈,不能只盯着业务逻辑,更要深挖底层 I/O…

2026/9/21 18:35:31 阅读更多 →

最新新闻

5个3GNET高频面试题拆解:告别文档迷宫实战指南

5个3GNET高频面试题拆解:告别文档迷宫实战指南

5个3GNET高频面试题拆解:告别文档迷宫实战指南 官方文档太长抓不住重点?别慌,这恰恰是许多开发者卡在 3GNET 技术栈上的死穴。…

2026/9/21 19:39:06 阅读更多 →
Cursor 加自定义模型,Base URL 填 TaoToken 地址

Cursor 加自定义模型,Base URL 填 TaoToken 地址

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/21 19:39:06 阅读更多 →
销售方式有几种类型面试必问3个坑新手避坑指南

销售方式有几种类型面试必问3个坑新手避坑指南

销售方式有几种类型面试必问3个坑新手避坑指南 看了一堆教程还是不会写项目,这是很多转行或入行不久开发者最大的痛点。你背了无数算法,刷了无数LeetCode,但一旦面试官问起业务场景中的“销售方式有几种类型”,或者让你设计一个通用的销售策略模…

2026/9/21 19:39:06 阅读更多 →
ubuntu WSL 下 cc-switch 乱码,让 Codex 走 TaoToken 查 locale 脚本行吗

ubuntu WSL 下 cc-switch 乱码,让 Codex 走 TaoToken 查 locale 脚本行吗

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/21 19:39:06 阅读更多 →
安徽双线服务器部署避坑指南:3个完整示例搞定高可用

安徽双线服务器部署避坑指南:3个完整示例搞定高可用

安徽双线服务器部署避坑指南:3个完整示例搞定高可用 刚学完 Python 语法,对着代码编辑器发呆?看着那些 import 和 def…

2026/9/21 19:39:06 阅读更多 →
舌尖毁了沈子钰实战避坑:3步搞定配置与高频面试题

舌尖毁了沈子钰实战避坑:3步搞定配置与高频面试题

舌尖毁了沈子钰实战避坑:3步搞定配置与高频面试题 配置环境就卡半天,是不是让你怀疑人生?明明照着文档敲,结果报错一堆,进度条转了半小时还没动静。这种痛苦,每个开发者都经历过。更尴尬的是,面试时遇到关于底层原理的 高频面试题…

2026/9/21 19:38:06 阅读更多 →

日新闻

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and …

2026/9/21 0:00:01 阅读更多 →
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,…

2026/9/21 0:00:01 阅读更多 →
Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

桌面应用AI 应用插件系统 【免费下载链接】Wox A cross-platform launcher that simply works 项目地址: https://gitcode.com/gh_mirrors/wo/Wox 点击查看 免费下载 全功能插件(Full-featured Plugin)是 Wox 三类插件实现方式中能力最完整的…

2026/9/21 0:00:01 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →