React-DnD 是 React 生态中处理拖放交互的“神器”,但刚接触时容易被“拖动源”“监视器”这些概念绕晕。其实它的核心逻辑很简单——就像现实中“拿东西放到指定位置”,每个概念都对应这个过程中的一个角色。今天用“待办清单”这个生活化场景,把7个核心概念讲明白,后续写代码时你会发现“原来都是老熟人”。
一、先建立认知:React-DnD的核心逻辑
先想一个日常场景:你有两张列表(“待处理”“已完成”)和几张待办卡片,用鼠标把卡片从“待处理”拖到“已完成”。这个过程在 React-DnD 中被拆分为3个核心角色和4个辅助工具:
-
核心角色: 被拖的“待办卡片”(拖动源)、接卡片的“列表”(拖放目标)、描述卡片的“信息标签”(项目)
-
辅助工具: 给角色分类的“标签类型”(类型)、观察拖放状态的“监控器”(监视器)、绑定角色和逻辑的“连接器”(连接器)、处理鼠标事件的“引擎”(拖放后端)
下面逐个拆解,每个概念都配“定义+实例”,保证一看就懂。
二、7个核心概念:逐个击破
1. 拖动源(Drag Source):让组件“能被拖”
定义: 给普通组件添加“可拖拽”能力的“功能包”,包裹后组件就能被鼠标点住拖动。
生活实例: 给待办卡片装上“可移动开关”,原本不能动的卡片,打开开关后就能用鼠标拖动了。
代码视角: 用 React-DnD 提供的 DragSource 高阶组件包裹待办卡片组件,卡片就拥有了拖拽能力。比如:
// 用DragSource包裹后,TodoCard就成了拖动源
const DraggableTodo = DragSource(类型, 逻辑配置, 收集函数)(TodoCard);
2. 拖放目标(Drop Target):让组件“能接拖”
定义: 给普通组件添加“接收拖放”能力的“功能包”,包裹后组件就能接收兼容的拖拽元素。
生活实例: 给“已完成”列表装上“接收托盘”,只有带特定标签的待办卡片拖过来时,托盘才会“接住”它。
代码视角: 用 DropTarget 高阶组件包裹列表组件,列表就成了能接收拖放的目标。比如:
// 用DropTarget包裹后,TodoList就成了拖放目标
const DroppableList = DropTarget(类型, 逻辑配置, 收集函数)(TodoList);
3. 项目(Item):拖动时传递的“核心信息”
定义: 描述被拖动内容的普通 JS 对象,只存核心信息(比如ID、标题),在拖动源和拖放目标之间传递数据。
生活实例: 拖待办卡片时,不用把整个卡片“搬过去”,只需要传递一张写着“卡片ID:1,标题:学习React-DnD”的小纸条,目标列表拿到纸条就知道该添加哪个卡片了。
代码实例: 拖待办卡片时的项目对象,简洁且只含关键信息:
{
todoId: 1, // 卡片唯一标识,必传
title: '学习React-DnD', // 辅助信息,可选
priority: 'high' // 其他需要传递的核心属性
}
注意:项目只存“需要传递的核心信息”,不用包含组件的样式、事件等冗余数据,避免性能浪费。
4. 类型(Type):拖动源和目标的“匹配暗号”
定义: 给项目分类的标识符(通常是字符串或 Symbol),用来指定“哪些拖动源能拖进哪些目标”,避免混乱。
生活实例: 给所有待办卡片贴“TODO”标签,给所有待办列表的托盘也标“TODO”暗号——只有标签和暗号匹配的卡片,托盘才会接收,防止把“用户头像”这类其他元素拖进待办列表。
代码实践: 通常用常量集中管理类型,方便复用和修改:
// 集中定义类型常量
export const ItemTypes = {
TODO: 'todo', // 待办卡片类型
USER: 'user', // 用户头像类型(后续扩展用)
FILE: 'file' // 文件类型(后续扩展用)
};
这样,待办卡片拖动源绑定 ItemTypes.TODO,待办列表目标也绑定 ItemTypes.TODO,两者就能匹配交互。
5. 监视器(Monitor):拖放状态的“观察者”
定义: 监听并获取拖放过程中各种状态的“工具”,能告诉组件“现在有没有东西在拖”“元素是不是悬停在我上面”等信息,组件靠这些信息调整UI。
生活实例: 给待办列表装一个“状态指示灯”——当有匹配标签的卡片悬停时,灯变绿;卡片能放在这里时,灯闪一下;没有卡片拖动时,灯保持熄灭。这个指示灯就是监视器的作用。
常用状态方法: 监视器提供一系列方法获取状态,新手先记这3个最常用的:
-
monitor.isDragging():判断当前元素是不是正在被拖动(可用来让拖动中的卡片变半透明)
-
monitor.isOver():判断有没有元素悬停在当前组件上(可用来让列表悬停时变背景色)
-
monitor.canDrop():判断悬停的元素能不能放在当前组件上(可用来让不能放的元素悬停时列表变红色)
6. 连接器(Connector):组件和逻辑的“绑定器”
定义: 把 React 组件的真实 DOM 节点和拖放逻辑绑定起来的“桥梁”,告诉 React-DnD“这个DOM节点是拖动源”“那个DOM节点是拖放目标”。
生活实例: 待办卡片装了“可移动开关”(拖动源),但需要一根“线”把开关和卡片本身连起来——这根线就是连接器,没有它,开关不知道该控制哪个卡片。
代码核心用法: 通过收集函数获取连接器方法,再绑定到组件的DOM节点上:
// 收集函数中获取连接器方法
function collect(connect, monitor) {
return {
// 绑定拖动源的方法
connectDragSource: connect.dragSource(),
// 绑定拖放目标的方法(目标组件用)
// connectDropTarget: connect.dropTarget(),
isDragging: monitor.isDragging()
};
}
// 组件中绑定DOM节点
function TodoCard({ connectDragSource, isDragging }) {
// 调用连接器方法,把div节点绑定为拖动源
return connectDragSource(
<div style={{ opacity: isDragging ? 0.5 : 1 }}>
待办卡片内容
</div>
);
}
7. 拖放后端(Backend):拖放的“底层引擎”
定义: 处理浏览器原生事件(鼠标按下、移动、松开)的“底层引擎”,把这些杂乱的原生事件转化为 React-DnD 能统一识别的内部逻辑。
新手不用深钻: 后端是 React-DnD 的“黑盒工具”,初学不用关心它怎么转化事件,只要知道“选对引擎”就行:
-
HTML5Backend:PC端默认引擎,基于浏览器原生拖放API,支持文件拖放,开箱即用。
-
TouchBackend:移动端引擎,处理触摸事件,适配手机和平板。
后续环境配置中,我们直接用 PC 端默认的 HTML5Backend,一行代码就能搞定。
三、概念串联:用一个场景理清关系
现在把所有概念串进“拖待办卡片”的场景,看看它们是怎么配合工作的:
-
给待办卡片组件用 DragSource 包裹,绑定类型 ItemTypes.TODO,成为“拖动源”;
-
拖动卡片时,beginDrag 方法生成“项目”对象 { todoId: 1, title: '学习React-DnD' };
-
给“已完成”列表用 DropTarget 包裹,也绑定 ItemTypes.TODO,成为“拖放目标”;
-
卡片悬停在列表上时,列表的“监视器”通过 isOver() 和 canDrop() 知道“有匹配卡片悬停且可放置”,触发列表背景变绿;
-
“连接器”分别把卡片DOM和拖动源绑定、列表DOM和拖放目标绑定,让底层“后端引擎”能监听事件;
-
松开鼠标时,列表的 drop 方法通过监视器的 getItem() 拿到项目信息,把卡片添加到“已完成”列表。