dnd-kit listeners干扰input:checkbox的onChange事件解决方案问询
dnd-kit拖拽监听器与复选框onChange事件冲突的解决方案
开发中使用dnd-kit实现拖拽功能时,发现父容器上的{...listeners}会干扰内部<input type="checkbox">和<input type="radio">的onChange事件触发,导致事件失效。注释掉{...listeners}后,复选框的onChange能正常工作,但该监听器是实现拖拽的必需项,因此需要同时保留拖拽功能和表单控件事件的解决方案。
问题复现代码
<div {...attributes} {...listeners} ref={setNodeRef} style={style} className={styles.itemWrapper} > <div className={styles.item}> <img src={item.image} alt="Image title goes here..." /> <input type="checkbox" id={item.id.toString()} onChange={() => { console.log('checked/unchecked') // 控制台无输出 handleSelection(item.id) }} /> <label htmlFor={item.id.toString()} ></label> </div> </div>
注释监听器后正常工作的代码
<div {...attributes} // {...listeners} // 已禁用 ref={setNodeRef} style={style} className={styles.itemWrapper} > <div className={styles.item}> <img src={item.image} alt="Image title goes here..." /> <input type="checkbox" id={item.id.toString()} onChange={() => { console.log('checked/unchecked') // 控制台有输出 handleSelection(item.id) }} /> <label htmlFor={item.id.toString()} ></label> </div> </div>
期望效果代码
<div {...attributes} {...listeners} ref={setNodeRef} style={style} className={styles.itemWrapper} > <div className={styles.item}> <img src={item.image} alt="Image title goes here..." /> <input type="checkbox" id={item.id.toString()} onChange={() => { console.log('checked/unchecked') // 启用{...listeners}时onChange事件需正常触发 handleSelection(item.id) }} /> <label htmlFor={item.id.toString()} ></label> </div> </div>
解决方案
方案1:阻止表单控件的事件冒泡
给复选框和关联标签添加onMouseDown和onClick事件,阻止事件冒泡到父容器的拖拽监听器,避免拖拽逻辑干扰表单事件:
<div {...attributes} {...listeners} ref={setNodeRef} style={style} className={styles.itemWrapper} > <div className={styles.item}> <img src={item.image} alt="Image title goes here..." /> <input type="checkbox" id={item.id.toString()} onChange={() => { console.log('checked/unchecked') handleSelection(item.id) }} onMouseDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()} /> <label htmlFor={item.id.toString()} onMouseDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()} ></label> </div> </div>
原理:dnd-kit的拖拽触发依赖mousedown等事件,阻止表单控件区域的事件冒泡后,父容器的拖拽监听器不会捕获到这些事件,从而让复选框的onChange正常执行。
方案2:自定义拖拽监听器,过滤表单控件事件
重写listeners中的mousedown事件,判断触发事件的目标是否为表单控件或其关联标签,若是则跳过拖拽逻辑:
const customListeners = { ...listeners, mousedown: (e) => { // 判断目标是否是复选框、单选框或对应的label const isFormControl = e.target.tagName === 'INPUT' && (e.target.type === 'checkbox' || e.target.type === 'radio') const isLabelForFormControl = e.target.tagName === 'LABEL' && ( document.getElementById(e.target.htmlFor)?.type === 'checkbox' || document.getElementById(e.target.htmlFor)?.type === 'radio' ) if (isFormControl || isLabelForFormControl) return // 非表单控件区域,执行原拖拽逻辑 listeners.mousedown(e) } } // 渲染时使用自定义监听器 <div {...attributes} {...customListeners} ref={setNodeRef} style={style} className={styles.itemWrapper} > {/* 内部内容不变 */} </div>
原理:在触发拖拽逻辑前先过滤掉表单控件相关的事件,既保留了拖拽功能,又不影响表单控件的事件触发。
内容的提问来源于stack exchange,提问作者Kazi Shariful Islam
相关产品推荐
相关产品推荐

