React中div元素的onDrop事件无法触发问题求助
React原生拖拽onDrop事件不触发问题
我正在使用React实现拖拽功能,但handleDrop函数始终未被调用,即onDrop事件无法正常工作,未找到解决办法。以下是我的代码片段:
import React, { useState, useRef } from 'react'; const App: React.FC = () => { const [cards, setCards] = useState([ { id: 1, title: 'Card 1', list: 'TODO' }, { id: 2, title: 'Card 2', list: 'TODO' }, { id: 3, title: 'Card 3', list: 'COMPLETED' }, { id: 4, title: 'Card 4', list: 'COMPLETED' }, ]); const dragItem = useRef<HTMLDivElement>(null); const [draggedOver, setDraggedOver] = useState<null | number>(null); const handleDragStart = (e: React.DragEvent<HTMLDivElement>, id: number) => { console.log("drag start"); dragItem.current = e.target as HTMLDivElement; e.dataTransfer.setData('id', id.toString()); }; const handleDragEnter = (id: number) => { console.log("entered"); setDraggedOver(id); }; const handleDragLeave = () => { console.log("leaved"); setDraggedOver(null); }; const handleDrop = (e: React.DragEvent<HTMLDivElement>, list: string) => { console.log("dropped"); e.preventDefault(); const id = Number(e.dataTransfer.getData('id')); const newCards = [...cards]; const card = newCards.find((c) => c.id === id); card!.list = list; setCards(newCards); setDraggedOver(null); }; return ( <div className="app"> <div className="lists"> <div className="list todo"> <h2>TODO</h2> {cards .filter((c) => c.list === 'TODO') .map((card) => ( <div key={card.id} className="card" draggable={true} onDragStart={(e) => handleDragStart(e, card.id)} onDragEnter={() => handleDragEnter(card.id)} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, 'COMPLETED')} ref={dragItem} > {card.title} </div> ))} </div> <div className="list completed"> <h2>COMPLETED</h2> {cards .filter((c) => c.list === 'COMPLETED') .map((card) => ( <div key={card.id} className="card" draggable={true} onDragStart={(e) => handleDragStart(e, card.id)} onDragEnter={() => handleDragEnter(card.id)} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, 'TODO')} > {card.title} </div> ))} </div> </div> </div> ) } export default App;
请问我遗漏了什么?注:我希望不使用第三方库实现此功能。
问题原因及修复方案
核心问题:未阻止拖拽事件的默认行为
浏览器默认会阻止拖拽元素放置到其他元素上,必须在onDragOver事件中调用e.preventDefault(),否则onDrop永远不会触发。你当前的代码里完全没有处理onDragOver事件,这是最关键的遗漏点。其他潜在问题
- 同一个
dragItemref被绑定到所有TODO列表的卡片上,导致ref仅指向最后一个渲染的卡片,且当前逻辑里这个ref没有实际作用,可直接删除。 handleDragLeave逻辑有缺陷:拖拽元素在目标元素的子元素间移动时也会触发该事件,导致draggedOver被错误置为null,可通过判断relatedTarget是否为目标元素后代优化。
- 同一个
修复步骤
1. 添加onDragOver处理函数
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); // 必须调用此方法才能触发onDrop };
2. 在所有卡片元素上绑定onDragOver
以TODO列表的卡片为例,修改后代码:
<div key={card.id} className="card" draggable={true} onDragStart={(e) => handleDragStart(e, card.id)} onDragEnter={() => handleDragEnter(card.id)} onDragLeave={handleDragLeave} onDragOver={handleDragOver} // 新增此行 onDrop={(e) => handleDrop(e, 'COMPLETED')} > {card.title} </div>
COMPLETED列表的卡片也需要添加同样的onDragOver绑定。
3. 移除多余的ref绑定
删除TODO列表卡片上的ref={dragItem},避免重复绑定导致的异常。
修复后的完整代码
import React, { useState } from 'react'; const App: React.FC = () => { const [cards, setCards] = useState([ { id: 1, title: 'Card 1', list: 'TODO' }, { id: 2, title: 'Card 2', list: 'TODO' }, { id: 3, title: 'Card 3', list: 'COMPLETED' }, { id: 4, title: 'Card 4', list: 'COMPLETED' }, ]); const [draggedOver, setDraggedOver] = useState<null | number>(null); const handleDragStart = (e: React.DragEvent<HTMLDivElement>, id: number) => { console.log("drag start"); e.dataTransfer.setData('id', id.toString()); }; const handleDragEnter = (id: number) => { console.log("entered"); setDraggedOver(id); }; const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => { console.log("leaved"); // 优化:仅当离开卡片本身时才重置状态 if (e.currentTarget === e.relatedTarget?.closest('.card')) return; setDraggedOver(null); }; const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); }; const handleDrop = (e: React.DragEvent<HTMLDivElement>, list: string) => { console.log("dropped"); e.preventDefault(); const id = Number(e.dataTransfer.getData('id')); // 用immutable方式更新数据,避免直接修改原对象 const newCards = cards.map(card => card.id === id ? {...card, list} : card ); setCards(newCards); setDraggedOver(null); }; return ( <div className="app"> <div className="lists"> <div className="list todo"> <h2>TODO</h2> {cards .filter((c) => c.list === 'TODO') .map((card) => ( <div key={card.id} className="card" draggable={true} onDragStart={(e) => handleDragStart(e, card.id)} onDragEnter={() => handleDragEnter(card.id)} onDragLeave={handleDragLeave} onDragOver={handleDragOver} onDrop={(e) => handleDrop(e, 'COMPLETED')} > {card.title} </div> ))} </div> <div className="list completed"> <h2>COMPLETED</h2> {cards .filter((c) => c.list === 'COMPLETED') .map((card) => ( <div key={card.id} className="card" draggable={true} onDragStart={(e) => handleDragStart(e, card.id)} onDragEnter={() => handleDragEnter(card.id)} onDragLeave={handleDragLeave} onDragOver={handleDragOver} onDrop={(e) => handleDrop(e, 'TODO')} > {card.title} </div> ))} </div> </div> </div> ) } export default App;
内容的提问来源于stack exchange,提问作者Chandler Bing
相关产品推荐
相关产品推荐

