Vue中使用Sortable的示例代码


Posted in Javascript onApril 07, 2018

之前开发一个后台管理系统,里面用到了Vue和Element-UI这个组件库,遇到一个挺有意思的问题,和大家分享一下。

场景是这样,在一个列表展示页上,我使用了Element-UI的表格组件,新的需求是在原表格的基础上支持拖拽排序。但是原有的组件本身不支持拖拽排序,而且由于是直接引入的Element-UI,不方便修改它的源码,所以比较可行的方法只能是直接操作DOM。

具体的做法是在mounted生命周期函数里,对this.$el进行真实DOM的操作,监听drag的一系列事件,在事件回调里移动DOM,并更新data。

HTML5 Drag事件还是挺多的,和Touch事件差不多,自己手工实现也可以,不过这里就偷了个懒,直接用了一个开源的Sortable库,直接传入this.$el,监听封装后的回调,并且根据Vue的开发模式,在移动DOM的回调里更新实际的Data数据,保持数据和DOM的一致性。

如果你以为到这就结束了,那就大错特错,偷过的懒迟早要还。。。本以为这个方案是很美好的,没想到刚想调试一下,就出现了诡异的现象:A和B拖拽交换位置之后,B和A又神奇得换回去了!这是怎么回事?似乎我们的操作没有什么问题,在真实DOM移动了之后,我们也移动了相应的data,数据数组的顺序和渲染出DOM的顺序应该是一致的。

问题出在哪里?我们回忆一下Vue的实现原理,在Vue2.0之前是通过defineProperty依赖注入和跟踪的方式实现双向绑定。针对v-for数组指令,如果指定了唯一的Key,则会通过高效的Diff算法计算出数组内元素的差异,进行最少的移动或删除操作。而Vue2.0之后在引入了Virtual Dom之后,Children元素的Dom Diff算法和前者其实是相似的,唯一的区别就是,2.0之前Diff直接针对v-for指令的数组对象,2.0之后则针对Virtual Dom。DOM Diff算法在这里不再赘述,这里解释的比较清楚virtual-dom diff算法

假设我们的列表元素数组是

[‘A','B','C','D']

渲染出来后的DOM节点是

[$A,$B,$C,$D]

那么Virtual Dom对应的结构就是

[{elm:$A,data:'A'},
 {elm:$B,data:'B'},
 {elm:$C,data:'C'},
 {elm:$D,data:'D'}]

假设拖拽排序之后,真实的DOM变为

[$B,$A,$C,$D]

此时我们只操作了真实DOM,改编了它的位置,而Virtual Dom的结构并没有改变,依然是

[{elm:$A,data:'A'},
 {elm:$B,data:'B'},
 {elm:$C,data:'C'},
 {elm:$D,data:'D'}]

此时我们把列表元素也按照真实DOM排序后变成

[‘B','A','C','D']

这时候根据Diff算法,计算出的Patch为,VNode前两项是同类型的节点,所以直接更新,即把$A节点更新成$B,把$B节点更新成$A,真实DOM又变回了

[$A,$B,$C,$D]

所以就出现了拖拽之后又被Patch算法更新了一次的问题,操作路径可以简单理解为

拖拽移动真实DOM -> 操作数据数组 -> Patch算法再更新真实DOM

根本原因

根本原因是Virtual DOM和真实DOM之间出现了不一致。

所以在Vue2.0以前,因为没有引入Virtual DOM,这个问题是不存在的。

在使用Vue框架的时候要尽量避免直接操作DOM

解决方案

1、通过设置key唯一标志每一个VNode,这也是Vue推荐的使用v-for指令的方式。因为在判断两个VNode是否为同类型时会调用sameVnode方法,优先判断key是否相同

function sameVnode (a, b) {
 return (
  a.key === b.key &&
  a.tag === b.tag &&
  a.isComment === b.isComment &&
  isDef(a.data) === isDef(b.data) &&
  sameInputType(a, b)
 )
}

2、因为根本原因是真实DOM和VNode不一致,所以可以通过把拖拽移动真实DOM的操作还原,即在回调函数里,把[$B,$A,$C,$D]还原成[$A,$B,$C,$D],让DOM的操作交还给Vue

拖拽移动真实DOM ->还原移动操作 -> 操作数据数组 -> Patch算法再更新真实DOM

代码如下

var app = new Vue({
    el: '#app', 
    mounted:function(){
      var $ul = this.$el.querySelector('#ul')
      var that = this
      new Sortable($ul, {
        onUpdate:function(event){
          var newIndex = event.newIndex,
            oldIndex = event.oldIndex
            $li = $ul.children[newIndex],
            $oldLi = $ul.children[oldIndex]
          // 先删除移动的节点
          $ul.removeChild($li)  
          // 再插入移动的节点到原有节点,还原了移动的操作
          if(newIndex > oldIndex) {
            $ul.insertBefore($li,$oldLi)
          } else {
            $ul.insertBefore($li,$oldLi.nextSibling)
          }
          // 更新items数组
          var item = that.items.splice(oldIndex,1)
          that.items.splice(newIndex,0,item[0])
          // 下一个tick就会走patch更新
        }
      })
    },
    data:function() {
      return {
        message: 'Hello Vue!',
        items:[{
          key:'1',
          name:'1'
        },{
          key:'2',
          name:'2'
        },{
          key:'3',
          name:'3'
        },{
          key:'4',
          name:'4'
        }]
      }
    },
    watch:{
      items:function(){
        console.log(this.items.map(item => item.name))
      }
    }
  })

3.暴力解决!不走patch更新,通过v-if设置,直接重新渲染一遍。当然不建议这么做,只是提供这种思路~

mounted:function(){
      var $ul = this.$el.querySelector('#ul')
      var that = this
      var updateFunc = function(event){
        var newIndex = event.newIndex,
          oldIndex = event.oldIndex
        var item = that.items.splice(oldIndex,1)
        that.items.splice(newIndex,0,item[0])

        // 暴力重新渲染!
        that.reRender = false
        // 借助nextTick和v-if重新渲染
        that.$nextTick(function(){
          that.reRender = true
          that.$nextTick(function(){
            // 重新渲染之后,重新进行Sortable绑定
            new Sortable(that.$el.querySelector('#ul'), {
              onUpdate:updateFunc
            })
          })
        })
      }
      new Sortable($ul, {
        onUpdate:updateFunc
      })
    },

所以,我们平时在使用框架的时候,也要去了解框架的实现原理的,否则遇到一些棘手的情况就会无从下手~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
特殊字符、常规符号及其代码对照表
Jun 26 Javascript
javascript ready和load事件的区别示例介绍
Aug 30 Javascript
Angular中使用ui router实现系统权限控制及开发遇到问题
Sep 23 Javascript
JavaScript Uploadify文件上传实例
Feb 28 Javascript
Angular通过angular-cli来搭建web前端项目的方法
Jul 27 Javascript
Bootstrap实现翻页效果
Nov 27 Javascript
react-router v4如何使用history控制路由跳转详解
Jan 09 Javascript
vue 实现边输入边搜索功能的实例讲解
Sep 16 Javascript
vsCode安装使用教程和插件安装方法
Aug 24 Javascript
使用vue开发移动端管理后台的注意事项
Mar 07 Javascript
通过GASP让vue实现动态效果实例代码详解
Nov 24 Javascript
解决VUE自定义拖拽指令时 onmouseup 与 click事件冲突问题
Jul 24 Javascript
JavaScript实现简单动态进度条效果
Apr 06 #Javascript
js+css实现打字效果
Jun 24 #Javascript
简单介绍react redux的中间件的使用
Apr 06 #Javascript
webpack源码之loader机制详解
Apr 06 #Javascript
vue.js项目nginx部署教程
Apr 05 #Javascript
常用的 JS 排序算法 整理版
Apr 05 #Javascript
通过 JS 判断页面是否有滚动条的实现方法
Apr 05 #Javascript
You might like
教你如何解密 “ PHP 神盾解密工具 ”
2014/06/20 PHP
ThinkPHP5框架中使用JWT的方法示例
2020/06/03 PHP
jquery插件 cluetip 关键词注释
2010/01/12 Javascript
利用百度地图JSAPI生成h7n9禽流感分布图实现代码
2013/04/15 Javascript
调用jQuery滑出效果时闪烁的解决方法
2014/03/27 Javascript
js动态添加表格数据使用insertRow和insertCell实现
2014/05/22 Javascript
JavaScript图像延迟加载库Echo.js
2016/04/05 Javascript
如何用JavaScript实现动态修改CSS样式表
2016/05/20 Javascript
jquery uploadify隐藏上传进度的实现方法
2017/02/06 Javascript
Require.js的基本用法详解
2017/07/03 Javascript
浅谈Node.js CVE-2017-14849 漏洞分析(详细步骤)
2017/11/10 Javascript
浅析JS抽象工厂模式
2017/12/14 Javascript
如何实现小程序tab栏下划线动画效果
2019/05/18 Javascript
layui富文本编辑器前端无法取值的解决方法
2019/09/18 Javascript
element-ui 本地化使用教程详解
2019/10/28 Javascript
详细解读Python中的__init__()方法
2015/05/02 Python
使用Pyinstaller的最新踩坑实战记录
2017/11/08 Python
python增加矩阵维度的实例讲解
2018/04/04 Python
python3+PyQt5实现支持多线程的页面索引器应用程序
2018/04/20 Python
Python获取指定字符前面的所有字符方法
2018/05/02 Python
利用python画出折线图
2018/07/26 Python
kaggle+mnist实现手写字体识别
2018/07/26 Python
Python退出时强制运行一段代码的实现方法
2020/04/29 Python
canvas拼图功能实现代码示例
2018/11/21 HTML / CSS
社区居务公开实施方案
2014/03/27 职场文书
团队精神口号
2014/06/06 职场文书
法人委托书
2014/07/31 职场文书
暑期培训班招生方案
2014/08/26 职场文书
2014年国庆节广播稿
2014/09/19 职场文书
2014审计局领导班子民主生活会对照检查材料思想汇报
2014/09/20 职场文书
2014年物流工作总结
2014/11/25 职场文书
汶川大地震感悟
2015/08/10 职场文书
2019公司管理制度
2019/04/19 职场文书
简单且有用的Python数据分析和机器学习代码
2021/07/02 Python
Redis调用Lua脚本及使用场景快速掌握
2022/03/16 Redis
剑指Offer之Java算法习题精讲二叉树专项训练
2022/03/21 Java/Android