Vue Draggable实战指南:拖拽排序与组件通信技巧

江洁 Dev 交互 阅读 1,042
赞 18 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

用 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 的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论