根本原因是搜索仅修改dom显示状态而未同步更新底层数据或checkbox绑定关系,导致sourcedata与可见列表脱节;必须保持节点不删除、遍历所有li(含隐藏项)获取checked状态、共享搜索逻辑、防抖兼顾空值处理、按钮状态依据显隐+勾选双重判断。

穿梭框搜索为什么总“搜得到却移不动”
根本原因是搜索时只操作了 DOM 显示状态,没同步更新底层数据或 checkbox 的绑定关系。典型现象:搜出“Vue”,勾选后点“向右移动”,结果移过去的是之前缓存的、未被过滤掉的“React”。sourceData 和当前可见 li 列表已经脱节。
必须守住两条线:
- 搜索过程只做
display: none或visibility: hidden,不从 DOM 删除节点,否则dataset.id丢失、事件监听失效 - 每次点击“向右移动”前,必须遍历**所有左侧
li(包括被隐藏的)** 中的input:checked,而不是只查当前显示的 - 搜索输入框的
input事件里,不能用innerHTML = ''重绘整个列表——这会清空所有 checkbox 状态和事件绑定
如何让左右两个列表共用同一套搜索逻辑
别写两套 searchLeft 和 searchRight 函数。共享一个搜索函数,靠上下文区分作用域更稳。
实操建议:
- 给左右两个
ul都加唯一 class,比如class="transfer-list transfer-left"和class="transfer-list transfer-right" - 搜索函数接收一个参数
side(值为"left"或"right"),然后用document.querySelectorAll(.transfer-${side} li)定位目标项 - 匹配逻辑统一走
textContent.toLowerCase().includes(keyword),避免正则搞错大小写或转义 - 隐藏/显示统一用
element.style.display = keyword ? 'list-item' : 'none',不要混用hidden属性或opacity
input 事件防抖但不能丢掉空值处理
用户快速输入时频繁触发搜索,DOM 重绘卡顿。加防抖是必须的,但很多人忘了:清空输入框时,要立刻恢复全部列表项,不能等防抖定时器结束。
正确做法:
- 用
setTimeout+clearTimeout实现防抖,延迟设为200ms左右 - 在事件回调开头加判断:
if (!keyword.trim()) { showAllItems(side); return; } -
showAllItems函数直接循环所有li并设style.display = 'list-item',不依赖原始数组顺序 - 别用
debounce工具函数封装后就不管上下文——它容易把side参数固化成第一次调用的值
搜索后“向右移动”按钮禁用状态不同步怎么办
按钮是否可用,不该由“有没有搜索结果”决定,而应由“当前是否有已勾选且未被隐藏的项”决定。常见错误是只检查 querySelectorAll('.transfer-left input:checked').length,但忽略了被搜索隐藏的项其实也还挂着 checked=true。
所以判断逻辑必须是:
- 先拿到所有左侧
li元素 - 对每个
li,检查它是否style.display !== 'none'且其内部input的checked === true - 只要有一个满足,就启用按钮;一个都不满足,就
btn.disabled = true - 这个检查必须在每次搜索完成、每次勾选变化后都执行,建议抽成独立函数
updateMoveButtonState('left')
真正难的不是写搜索,而是让搜索、勾选、移动、按钮状态四者始终咬合。任何一环用 DOM 快照代替数据驱动,后面就会开始“看起来能动,实际移错项”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











