You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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事件,这是最关键的遗漏点。

  • 其他潜在问题

    1. 同一个dragItem ref被绑定到所有TODO列表的卡片上,导致ref仅指向最后一个渲染的卡片,且当前逻辑里这个ref没有实际作用,可直接删除。
    2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:55:22