React-DnD
React 拖拽库的"元老级"方案,以极致的灵活性和后端抽象著称,适合高度自定义的复杂拖拽场景。
React 生态早期的拖拽库,采用 Flux 数据模式设计,通过 Drag Source、Drop Target 等原语实现高度自定义的复杂拖拽场景。
React-DnD
React 拖拽库的"元老级"方案,以极致的灵活性和后端抽象著称,适合高度自定义的复杂拖拽场景。
技术简介说明
React-DnD 是 React 生态中最早期的拖拽库之一,由 Redux 作者 Dan Abramov 于 2015 年创建,后由 Jordan Gensler 和 Chris Trevino 维护。它采用独特的 Flux 数据模式设计,将拖拽状态管理独立于 React 组件之外,通过 actions 和 reducers 管理拖拽生命周期。
React-DnD 的核心设计哲学是极致的灵活性——它不预设任何拖拽模式(列表、网格、看板),而是提供最底层的原语:Drag Source(拖拽源)、Drop Target(放置目标)、Drag Layer(拖拽层)和 Backend(后端抽象)。开发者通过这些原语自由组合出任何拖拽场景,从简单的排序列表到复杂的棋盘游戏。
然而,这种灵活性也带来了陡峭的学习曲线和冗长的 API。在 2025-2026 年,React-DnD 已经4 年未发布新版本(v16.0.1 发布于 2022 年 4 月),且未官方支持 React 19。社区正逐步转向 dnd-kit 和 Atlassian 的 Pragmatic drag-and-drop。
基本信息
- 官网: https://react-dnd.github.io/react-dnd
- GitHub: https://github.com/react-dnd/react-dnd
- License: MIT
- 最新版本: v16.0.1(2022 年 4 月,约 4 年前)
- 主要维护者: Jordan Gensler(@kesne)、Chris Trevino(@darthtrevino)
- 原始创建者: Dan Abramov(Redux 作者)
- npm 周下载量: ~450 万
- GitHub Stars: 21,600+
快速上手
安装
# 安装核心库和 HTML5 后端
npm install react-dnd react-dnd-html5-backend
# 如需触摸设备支持
npm install react-dnd-touch-backend
# 如需测试支持
npm install react-dnd-test-backend
# 多后端自动切换(桌面 + 移动端)
npm install react-dnd-multi-backend基础配置
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
function App() {
return (
<DndProvider backend={HTML5Backend}>
{/* 所有拖拽组件必须在此 Provider 内 */}
<Board />
</DndProvider>
)
}最小示例
import { useDrag, useDrop, DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
const ItemTypes = {
CARD: 'card',
}
// 可拖拽的卡片组件
function Card({ id, text, index, moveCard }) {
const [{ isDragging }, dragRef] = useDrag({
type: ItemTypes.CARD,
item: { id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
})
const [, dropRef] = useDrop({
accept: ItemTypes.CARD,
hover: (draggedItem) => {
if (draggedItem.index !== index) {
moveCard(draggedItem.index, index)
draggedItem.index = index
}
},
})
return (
<div
ref={(node) => { dragRef(node); dropRef(node) }}
style={{ opacity: isDragging ? 0.5 : 1 }}
>
{text}
</div>
)
}
// 主组件
export function SortableCards() {
const [cards, setCards] = useState([
{ id: 1, text: 'Card 1' },
{ id: 2, text: 'Card 2' },
{ id: 3, text: 'Card 3' },
])
const moveCard = (fromIndex, toIndex) => {
setCards((prev) => {
const newCards = [...prev]
const [removed] = newCards.splice(fromIndex, 1)
newCards.splice(toIndex, 0, removed)
return newCards
})
}
return (
<DndProvider backend={HTML5Backend}>
{cards.map((card, index) => (
<Card
key={card.id}
id={card.id}
text={card.text}
index={index}
moveCard={moveCard}
/>
))}
</DndProvider>
)
}核心概念与架构
架构设计
React-DnD 基于 Flux 数据模式设计,将拖拽状态管理独立于 React:
┌───────────────────────────────────────┐
│ DndProvider │
│ (提供拖拽上下文,绑定 Backend) │
├───────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌────────────────┐ │
│ │ DragSource │ │ DropTarget │ │
│ │ (拖拽源) │ │ (放置目标) │ │
│ │ useDrag() │ │ useDrop() │ │
│ └──────┬──────┘ └──────┬─────────┘ │
│ │ │ │
│ ┌──────▼────────────────▼─────────┐ │
│ │ DragDropManager │ │
│ │ (独立于 React 的状态管理) │ │
│ │ actions + reducers 模式 │ │
│ └──────────────┬──────────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────┐ │
│ │ Backend │ │
│ │ (HTML5 | Touch | Test) │ │
│ └─────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ DragLayer │ │
│ │ (自定义拖拽预览层) │ │
│ │ useDragLayer() │ │
│ └─────────────────────────────────┘ │
└───────────────────────────────────────┘
核心 Hooks API
| Hook | 功能 | 返回值 |
|---|---|---|
useDrag | 使组件成为可拖拽源 | [collectedProps, dragRef, dragPreviewRef] |
useDrop | 使组件成为放置目标 | [collectedProps, dropRef] |
useDragLayer | 监听拖拽状态,用于自定义预览层 | collectedProps |
Backend 系统
Backend 是 React-DnD 最独特的设计——它将底层输入检测抽象为可替换的后端:
| Backend | 功能 | 说明 |
|---|---|---|
| HTML5Backend | 默认桌面后端 | 使用 HTML5 Drag API,支持跨应用拖拽 |
| TouchBackend | 触摸设备后端 | 支持移动端触摸拖拽 |
| TestBackend | 测试后端 | 可编程模拟拖拽交互,无需 DOM |
| MultiBackend | 多后端切换 | 自动检测输入类型并切换(社区包) |
核心数据模型
| 概念 | 说明 |
|---|---|
| Items | 纯 JavaScript 对象,表示拖拽的数据({ id, index, type }) |
| Types | 字符串类型,用于匹配拖拽源和放置目标的兼容性 |
| Monitors | 监控拖拽状态变化,提供 isDragging()、canDrop() 等方法 |
| Collect 函数 | 从 Monitor 中提取状态并注入组件 props |
事件生命周期
拖拽开始 (beginDrag)
↓
拖拽中 (hover) ← 高频触发
↓
放置 (drop)
↓
拖拽结束 (endDrag)
核心特性
- 极致灵活性:最底层的拖拽原语,可构建任何拖拽场景(棋盘、卡片游戏、看板、文件管理器)
- Backend 系统:将输入检测抽象为可替换后端,支持 HTML5、触摸、测试等多种后端
- DragLayer 自定义预览:完全控制拖拽预览效果,可渲染任意 React 组件作为预览
- TestBackend:无需 DOM 即可测试拖拽逻辑,单元测试友好
- TypeScript 原生支持:完整的类型定义
- Flux 数据模式:拖拽状态独立于 React,支持复杂的状态流转
- 经过实战检验:大规模采用,npm 周下载量 ~450 万,大量生产项目使用
- 多后端支持:通过
react-dnd-multi-backend自动切换桌面/移动端 - 细粒度控制:完全控制拖拽生命周期每个阶段
生态图
┌──────────────────┐
│ react-dnd │ ← 核心库
│ v16.0.1 │
└────────┬─────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌────────▼──────┐ ┌──────▼───────┐ ┌──────▼────────┐
│ HTML5 Backend │ │Touch Backend │ │ Test Backend │
│ (桌面拖拽) │ │ (移动端) │ │ (单元测试) │
└───────────────┘ └──────────────┘ └───────────────┘
社区生态:
┌──────────────────────────────────────────────┐
│ • react-dnd-multi-backend (多后端自动切换) │
│ • react-dnd-preview (自定义拖拽预览) │
│ • react-dnd-scroll-mouse (滚动支持) │
│ • 大量教程和社区示例 │
└──────────────────────────────────────────────┘
已废弃的相关库:
┌──────────────────────────────────────────────┐
│ • react-beautiful-dnd ❌ (Atlassian 弃用) │
│ • react-sortable-hoc ❌ (停止维护) │
└──────────────────────────────────────────────┘
关键依赖
- react-dnd-html5-backend:默认后端,使用 HTML5 Drag API
- react-dnd-touch-backend:触摸设备后端(~100 万周下载)
- react-dnd-test-backend:测试后端(无 DOM 依赖)
- react:需要 React ≥ 16.8(Hooks 支持)
适用场景
- 棋盘类游戏:官方经典示例,棋子在网格间拖拽,利用 Type 系统控制兼容性
- 看板系统(Kanban):任务卡片在列间拖拽,利用
useDrag+useDrop组合 - 卡片游戏:复杂的多对多拖拽关系,需要精细控制拖拽行为
- 自定义拖拽预览:需要完全控制预览效果,使用
useDragLayer渲染自定义组件 - 多上下文拖拽:不同区域接受不同类型的拖拽项(利用 Type 匹配系统)
- 需要测试拖拽逻辑:TestBackend 支持无 DOM 单元测试
- 需要桌面文件拖入:HTML5Backend 支持从桌面拖入文件(区别于 dnd-kit)
开发与工程化
开发流程
# 克隆仓库
git clone https://github.com/react-dnd/react-dnd.git
cd react-dnd
# 安装依赖
yarn install
# 启动开发文档站
yarn start
# 构建
yarn build
# 测试
yarn test项目结构
react-dnd/
├── packages/
│ ├── core/ # react-dnd 核心
│ │ └── src/
│ │ ├── hooks/ # useDrag, useDrop, useDragLayer
│ │ ├── common/ # 类型定义、DndProvider
│ │ └── internals/ # DragDropManager
│ ├── backend-html5/ # HTML5 后端
│ ├── backend-touch/ # 触摸后端
│ ├── backend-test/ # 测试后端
│ └── test-utils/ # 测试工具
├── examples/ # 示例项目
│ ├── chessboard/ # 棋盘
│ ├── kanban/ # 看板
│ └── card-game/ # 卡片游戏
└── docs/ # 文档
性能与安全
性能特点
| 指标 | 表现 | 说明 |
|---|---|---|
| Bundle 大小 | ~23KB (gzipped) | 大于 dnd-kit (~6KB) |
| 100 项 | 流畅 | 无明显性能问题 |
| 500 项 | 可接受 | 部分掉帧 |
| 1000+ 项 | ~45fps | 性能明显下降 |
| 渲染方式 | 位置更新 | 不如 CSS transform + GPU 加速 |
| Tree-shakeable | 部分 | 不如 dnd-kit 完全支持 |
性能优化建议
-
使用
useDragLayer自定义预览:避免拖拽时重渲染整个组件树function CustomDragLayer() { const { item, offset } = useDragLayer((monitor) => ({ item: monitor.getItem(), offset: monitor.getClientOffset(), })) // 渲染自定义预览 } -
避免 collect 函数过度调用:collect 函数在每次状态变化时都会执行,保持轻量
-
使用
React.memo优化:减少不必要的子组件重渲染 -
大数据集配合虚拟化:1000+ 项时性能下降,需配合虚拟滚动库
安全注意事项
- React-DnD 不涉及用户输入,无 XSS 风险
- SSR 注意:Issue #1721 报告 SSR 中存在内存泄漏,需正确处理
DndProvider生命周期 - 拖拽 Item 数据避免包含敏感信息
已知性能瓶颈
- 1000+ DropTarget:所有 DropTarget 的 collect 函数都会调用,影响性能(Issue #421)
- 高频
hover事件:拖拽过程中 hover 高频触发,如在 hover 中执行 state 更新可能性能问题 - CSS transform 未优化:不如 dnd-kit 的 GPU 加速方案
技术对比
与 React 拖拽库对比
| 维度 | React-DnD | dnd-kit | react-beautiful-dnd | Pragmatic DnD |
|---|---|---|---|---|
| 维护状态 | ⚠️ 4 年无更新 | ✅ 活跃 | ❌ 已弃用 | ✅ Atlassian |
| 包体积 | ~23KB | ~6KB | ~30KB | ~3.5KB |
| 学习曲线 | 陡峭 | 中等 | 简单 | 中等 |
| 上手时间 | 4-6 小时 | 3-4 小时 | 2-3 小时 | 3-4 小时 |
| 灵活性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 无障碍 | ⚠️ 需手动 | ✅ 内建 | ✅ 键盘 | ⚠️ 需手动 |
| 触摸支持 | ⚠️ 需 TouchBackend | ✅ 原生 | ⚠️ 有限 | ⚠️ 需配置 |
| HTML5 DnD | ✅ 使用 | ❌ 不使用 | ❌ 不使用 | ✅ 使用 |
| 外部文件拖入 | ✅ 支持 | ❌ 不支持 | ❌ 不支持 | ✅ 支持 |
| 虚拟化 | ⚠️ 需自行实现 | ✅ 原生 | ⚠️ 差 | ✅ 原生 |
| React 19 支持 | ❓ 未验证 | ✅ 支持 | ❌ 不支持 | ✅ 支持 |
选型建议
| 场景 | 推荐 |
|---|---|
| React 项目通用拖拽 | dnd-kit(默认选择) |
| 需要桌面文件拖入 | React-DnD 或 Pragmatic DnD |
| 复杂自定义拖拽(棋盘、卡片游戏) | React-DnD(灵活性最强) |
| 遗留 React-DnD 项目 | 短期继续使用,中期评估迁移到 dnd-kit |
| 新项目 | ❌ 不推荐 React-DnD(维护停滞) |
最佳实践
生产环境最佳实践
-
始终使用 Hooks API:装饰器 API(
@DragSource、@DropTarget)已过时// ✅ 推荐:Hooks API const [{ isDragging }, dragRef] = useDrag({ ... }) // ❌ 避免:装饰器 API(已过时) @DragSource('card', spec, connect) class Card extends React.Component { } -
正确放置
DndProvider:必须在所有拖拽组件的外部// ✅ 正确 <DndProvider backend={HTML5Backend}> <App /> </DndProvider> // ❌ 错误:不能在 App 内部 function App() { return <DndProvider backend={HTML5Backend}>...</DndProvider> } -
使用 TestBackend 做测试:避免依赖 DOM
import { TestBackend } from 'react-dnd-test-backend' render( <DndProvider backend={TestBackend}> <MyComponent /> </DndProvider> ) -
多后端场景用
react-dnd-multi-backend:自动切换桌面/移动端 -
使用 DragLayer 自定义预览:减少不必要的重渲染
-
合理定义 Item Type:Type 是字符串常量,建议使用常量对象集中管理
const ItemTypes = { CARD: 'card', COLUMN: 'column' } as const
常见陷阱
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 忘记 DndProvider | 导致 "Expected drag drop context" 错误 | 确保 DndProvider 在组件树顶层 |
| SSR 内存泄漏 | Issue #1721 报告 SSR 内存泄漏 | 正确处理 DndProvider 生命周期 |
| React 19 兼容性 | 未官方支持(Issue #3655) | 监控兼容性,或迁移到 dnd-kit |
| collect 函数过度使用 | 所有 DropTarget 都调用 collect | 保持 collect 函数轻量 |
| hover 中频繁 setState | 高频 hover 事件导致性能问题 | 使用 React.memo 或节流 |
| Item Type 不匹配 | Drag Source 和 Drop Target 的 type 不一致 | 使用常量集中管理 Type |
| 移动端无触摸支持 | 忘记安装 TouchBackend | 使用 react-dnd-multi-backend |
技术局限与边界
已知限制
| 限制 | 说明 |
|---|---|
| 维护停滞 | 4 年无新版本(v16.0.1,2022 年 4 月) |
| React 19 未知 | Issue #3655 仍开放,无官方回应 |
| 学习曲线陡峭 | 需要理解 Monitors、Backends、Item/Type 系统 |
| API 冗长 | 相比 dnd-kit 更复杂,样板代码多 |
| 无障碍需手动实现 | 无内建 ARIA 支持、键盘导航、屏幕阅读器通知 |
| 触摸支持需额外 Backend | 不像 dnd-kit 原生支持 |
| Bundle 较大 | ~23KB vs dnd-kit ~6KB |
| 性能瓶颈 | 1000+ 项时性能下降至 ~45fps |
| 内置排序能力有限 | 需要额外实现排序逻辑 |
不适用场景
- 简单排序列表 → 推荐 dnd-kit(开箱即用)
- 无障碍优先项目 → 推荐 dnd-kit(内建 WCAG 2.1 AA)
- 新项目(2026 年) → 推荐 dnd-kit 或 Pragmatic DnD
- 大规模数据(>1000 项) → 推荐 dnd-kit(性能更好)
迁移注意事项
从 React-DnD 迁移到 dnd-kit:
- 预计 1-2 天(中等复杂度项目)
- 主要差异:React-DnD 使用 HTML5 DnD API,dnd-kit 使用自定义指针事件
- dnd-kit 不支持从桌面拖入文件,如需此功能考虑 Pragmatic DnD
学习资源
- 官方文档:https://react-dnd.github.io/react-dnd
- 包含教程、API 文档、示例
- 官方示例:棋盘、看板、卡片游戏
- GitHub 仓库:https://github.com/react-dnd/react-dnd
- 官方示例代码:https://github.com/react-dnd/react-dnd/tree/main/examples
- YouTube 教程:React Drag And Drop Tutorial - React-DND Made Simple (2025)
- Medium 文章:Easy Drag and Drop in React
- 中文教程:知乎专栏 - 如何用 react-dnd 的 hooks API 写拖拽
- Stack Overflow:大量 Q&A 解决方案
- StackBlitz:在线可运行示例
2026 年现状
维护状态评估
| 指标 | 状态 |
|---|---|
| 最新版本 | v16.0.1(2022 年 4 月) |
| 距上次发布 | ~4 年 |
| npm 周下载量 | ~450 万(大量存量项目) |
| GitHub Stars | 21,600+ |
| Open Issues | 444 |
| React 19 支持 | ❓ 未验证,Issue #3655 无回应 |
| 维护活跃度 | ⚠️ 事实上已停滞 |
社区态度
正面:
- 仍被用于复杂拖拽场景(棋盘、卡片游戏)
- react-beautiful-dnd 废弃后,部分用户迁移到 React-DnD
- 444 个 open issues 显示仍有活跃用户基础
- 450 万周下载量说明大量存量项目
负面:
- 2026 年多篇文章不再推荐 React-DnD 作为首选
- 社区转向 dnd-kit 或 Pragmatic drag-and-drop
- React 19 兼容性问题未解决引发担忧
- 4 年无 npm 发布,无安全补丁或新功能
发展趋势
- 事实上已停滞:虽有 GitHub 活动,但无实际发布
- 被新库替代:dnd-kit 成为 React 拖拽的默认选择
- 存量项目仍可靠:450 万周下载量,短期内不会消失
- 新项目不推荐:除非确实需要 HTML5 DnD API 的极端灵活性
选型结论
React-DnD 在 2026 年事实上已停滞维护(4 年无更新,React 19 未验证)。 新项目应选择 dnd-kit 或 Pragmatic drag-and-drop。遗留项目短期可继续使用,中期建议评估迁移到 dnd-kit。唯一仍推荐 React-DnD 的场景是需要 HTML5 DnD API(桌面文件拖入)且需要极致灵活性的复杂自定义拖拽。