Vue Draggable实战指南:拖拽排序与组件通信技巧
我的写法,亲测靠谱
用 Vue Draggable 做拖拽排序功能,我前后折腾过不下五六个项目。一开始图快,直接照着文档抄,结果上线后各种诡异问题:列表抖动、数据错乱、移动端点不动……后来慢慢摸出门道,现在基本一套写法走天下,稳定又省心。
我现在的标准写法长这样:
<template>
<draggable
v-model="list"
:animation="150"
:group="{ name: 'items', pull: 'clone', put: true }"
:sort="true"
item-key="id"
@start="drag = true"
@end="drag = false"
>
<template #item="{ element }">
<div class="item">{{ element.name }}</div>
</template>
</draggable>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: { draggable },
data() {
return {
drag: false,
list: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
]
}
}
}
</script>
关键点有几个:第一,必须用 v-model 而不是 :list。早期版本文档里还混用这两种方式,但新版(v4+)明确推荐 v-model,它能自动处理内部状态同步,避免手动调用 splice 导致的响应式失效。第二,item-key 别偷懒,哪怕你数据有唯一 ID 也得显式指定,否则控制台警告刷屏,严重时还会导致 DOM 复用错乱。第三,@start 和 @end 我习惯加个 drag 标志位,方便在其他地方做交互反馈,比如禁用按钮、高亮区域等。
这几种错误写法,别再踩坑了
我见过太多人栽在这些坑里,自己也中过招,列几个典型反面教材:
- 直接操作原始数组:比如在
@end里手动this.list.splice(...)。Vue Draggable 内部已经通过v-model同步了数据,你再手动改,等于绕过它的 diff 机制,轻则 UI 不更新,重则数据和视图完全脱节。除非你用的是旧版且没用v-model,否则千万别这么干。 - 忽略 key 的稳定性:有人图省事用
index当 key,拖拽时列表顺序一变,key 全乱套,Vue 会疯狂重建 DOM,性能差不说,输入框焦点、滚动位置全丢。记住,key必须是数据本身的唯一标识,比如数据库 ID。 - 在拖拽过程中发请求:有些同学喜欢在
@end里立刻调 API 保存顺序。但如果你的列表是从接口拉的,而用户拖拽时网络慢,可能还没保存完又拖了一次,导致覆盖。我的做法是加个防抖,或者先本地更新,等用户停手 500ms 再提交。比如:
// 别这么干
onEnd() {
this.saveOrderToServer(this.list)
}
// 这样更稳
onEnd() {
clearTimeout(this.saveTimer)
this.saveTimer = setTimeout(() => {
this.saveOrderToServer(this.list)
}, 500)
}
实际项目中的坑
实战中总有意想不到的问题。比如有一次做后台管理系统的菜单排序,拖拽后后端返回 200,但前端列表没变。查了半天发现,后端返回的数据结构和前端不一致——前端传的是 [{id:1}, {id:2}],后端回的是 [1, 2]。我直接赋值 this.list = res.data,结果 Vue 没触发响应式更新。后来改成只更新顺序,保留原对象引用:
// 假设后端返回 [2, 1] 表示新顺序
const newOrder = [2, 1]
const reorderedList = newOrder.map(id =>
this.list.find(item => item.id === id)
)
this.list = reorderedList
还有一次在移动端,列表嵌在可滚动容器里,拖拽时整个页面跟着滚。这是因为 Draggable 默认没处理 touch 事件冒泡。解决方案是在容器上加 touch-action: none,但要注意别影响其他交互。或者用 :prevent-on-filter="false" 配合自定义过滤器,不过这个配置有点 tricky,我一般优先用 CSS 解决。
另外,如果你的列表项里有输入框、下拉菜单这类可交互元素,记得加 :filter="'.ignore-drag'" 并给这些元素加 class,否则点输入框时会触发拖拽。比如:
<draggable
:filter="'.ignore-drag'"
:prevent-on-filter="false"
>
<template #item="{ element }">
<div class="item">
<input class="ignore-drag" v-model="element.name" />
</div>
</template>
</draggable>
这里 prevent-on-filter 设为 false 很关键,否则点击输入框会被阻止默认行为,导致无法聚焦。
性能别忽视,大数据量要小心
虽然 Draggable 本身性能不错,但如果列表超过 100 项,拖拽时还是会卡。我试过两种优化:一是用虚拟滚动,但和 Draggable 集成很麻烦,官方也不推荐;二是限制可拖拽区域,比如只允许拖前 20 项,或者分页加载。最简单有效的还是加个 loading 状态——拖拽开始时显示半透明遮罩,结束再隐藏,至少让用户感觉“系统在工作”,而不是卡死。
对了,动画时间 :animation 别设太高,150ms 左右刚好,设成 500ms 以上用户会觉得拖拽粘滞,体验反而差。
结尾唠叨
以上是我踩坑后的总结,核心就三点:用 v-model、保证 key 稳定、别在拖拽中直接操作数据。其他都是细节打磨。这个库其实挺稳的,只要避开那些反模式,基本不会出大问题。当然,如果你的场景特别复杂(比如跨组件拖拽、嵌套拖拽),可能需要结合 Sortable.js 原生 API,但那是另一个故事了。
以上是我个人对 Vue Draggable 的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

暂无评论