React 拖拽排序别再用原生 DOM 了:React SortableJS 10 分钟上手指南
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
React SortableJS是SortableJS之上的React绑定层,负责把DOM拖拽事件和你的组件状态对齐。做拖拽列表或看板时,你不用手写鼠标、触摸、滚动的逻辑,装包、包一层组件就能用。
它替你省掉了什么
手写拖拽用addEventListener很麻烦:拖一项状态跟不上还闪;卸载组件监听还在、会泄漏;触摸、滚动也得自己搞。React SortableJS把这三个坑收走了——传数组和setter就行,剩下的它代劳。你不用去读它的内部实现,装完就能把精力放回业务逻辑上,列表顺序、后端同步这些才是你真正要操心的事。
⚡ react-sortablejs 教程:从装包到第一个能拖的列表
最快的验证方式,就是先让三行字动起来。一行装包:
npm install react-sortablejs sortablejs再写一个最小组件:
import { useState } from "react"; import { ReactSortable } from "react-sortablejs"; export function TaskList() { const [tasks, setTasks] = useState([ { id: "a", title: "写需求" }, { id: "b", title: "评审方案" }, { id: "c", title: "开始开发" }]); return ( <ReactSortable list={tasks} setList={setTasks} animation={150}> {tasks.map((t) => <div key={t.id}>{t.title}</div>)} </ReactSortable> ); }改 list / setList / animation 三个 prop 就能接你自己的业务:list 是你的数组,setList 是你数组对应的 setter,animation 是落位过渡的毫秒数。拖完 React 会调用 setList 把新顺序写回状态,列表就跟着变了,你不用碰任何 DOM。
📋 React 拖拽列表的高频配置速查
能跑起来之后你会被一堆 prop 淹没,我平时只记这几个,够覆盖 90% 的场景:
| prop | 干什么 | 推荐值 | 一句话备注 |
|---|---|---|---|
| list / setList | 数据源与写回 | 你的数组 + setState | 拖拽生效的一对,必填 |
| animation | 落位过渡时长 | 150 | 太快生硬、太慢发飘 |
| ghostClass | 拖拽时"空位"样式 | sortable-ghost | 控制那个洞长什么样 |
| handle | 限定可拖区域的选择器 | .drag-handle | 只让把手能拖,防误触 |
| group | 跨列联动开关 | { name: "tasks" } | 列名一致才能互相放 |
| delay / delayOnTouchStart | 按住多久才起拖 | 100 / true | 移动端必加,否则挡滚动 |
| forceFallback | 用 CSS 变换模拟拖拽 | 大列表开 true | 避免真实 DOM 拖动卡顿 |
| onEnd | 拖拽结束回调 | (e) => 保存 | 在这里把新顺序同步后端 |
📊 看板拖拽多列联动怎么写
从单列表进真实产品,一般就两种需求:多列之间搬项目,或者单列里带约束地重排。先看看板,两列都挂同一个 group 名:
<ReactSortable list={todo} setList={setTodo} group={{ name: "kanban", pull: true, put: true }} animation={150}> {todo.map((t) => <div key={t.id}>{t.title}</div>)} </ReactSortable> <ReactSortable list={doing} setList={setDoing} group={{ name: "kanban", pull: true, put: true }} animation={150}> {doing.map((t) => <div key={t.id}>{t.title}</div>)} </ReactSortable>这里 group 的 name 必须两列一致,SortableJS 靠它判断两个容器属不属于同一组,名字不同就认不出来,自然放不进去;pull / put 都设 true 是允许"既能拉出、又能放进来",只开一个方向就会变成单向通道。列与列之间不直接通信,它们都只监听同一个 group 名,这是看板拖拽实现的要害。顺便说,移动端还要补 delay={100} delayOnTouchStart,不然手指一触就起拖,跟上下滚列打架,体验会很糟。
第二个场景是单列表带把手重排,我一般直接这样配,既防误触又能锁住个别行:
<ReactSortable list={rows} setList={setRows} handle=".row-drag" filter=".row-actions" preventOnFilter onMove={(e) => !rows[e.relatedIndex]?.locked} animation={150}> {rows.map((r) => ( <div key={r.id} className="row"> <span className="row-drag" />{r.title} </div> ))} </ReactSortable>handle 把可拖区域锁死在左侧把手上,点行内正文或按钮就不会误触发拖拽;filter 加 preventOnFilter 把操作列排除掉,同时避免点击事件被吞。onMove 里判断目标行是否 locked,返回 false 就拦下这次放置,于是"有的行不让动"这种约束一行就写完了,不用自己监听拖拽过程。
🕳 react 列表拖拽四个我踩过的坑
坑一:症状是拖完某行,文字内容错位了,像串到了别的行。根因是 key 用了 index,React 调和时复用了旧节点。修复:key={item.id}。
坑二:症状是手机上列表要么滑不动、要么一碰就拖。根因是触摸设备没设延迟,拖拽和滚动在抢同一个手势。修复:补上 delay={100} delayOnTouchStart。
坑三:症状是看板两列之间拖过去又弹回来,或者根本放不进去。根因是两列 group 的 name 不一致,或 put 是 false。修复:统一成 group={{ name: "kanban", pull: true, put: true }}。
坑四:症状是两百行的列表一拖就掉帧,整页都在重渲染。根因是每次拖拽都更新数组,子项组件跟着全部重算。修复:把行组件用 React.memo 包一层,并保证 id 稳定。
接下来看什么
到这里,你页面上已经有一个能拖、能同步状态的 React 拖拽排序列表了。想看看真实产品里拖拽是怎么落地、以及这个仓库前端的目录约定,可以直接翻下面两处,比啃教程更快。
- 通用拖拽控制器实现:generic-drag-and-drop.controller.ts
- 前端开发约定:frontend/doc/README.md
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考