当前位置: 移动技术网 > IT编程>脚本编程>vue.js > Vue中使用Sortable的示例代码

Vue中使用Sortable的示例代码

2018年04月25日  | 移动技术网IT编程  | 我要评论

侯杰,黄章和雷军,短信群发平台公司

之前开发一个后台管理系统,里面用到了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算法在这里不再赘述,这里解释的比较清楚

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

[‘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
      })
    },

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网