Vue中使用Sortable

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue中使用Sortable相关的知识,希望对你有一定的参考价值。

参考技术A

之前开发一个后台管理系统,里面用到了 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算法

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

渲染出来后的DOM节点是

那么Virtual Dom对应的结构就是

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

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

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

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

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

根本原因是 Virtual DOM 和 真实DOM 之间出现了不一致。
所以在Vue2.0以前,因为没有引入 Virtual DOM ,这个问题是不存在的。
在使用Vue框架的时候要尽量避免直接操作DOM

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

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

Sortable.js在vue中实现拖拽

1 npm install sortablejs --save 

2 xxx.vue

    <template>
        <div>
            <el-bgwhite>
                <el-row>
                    <el-form
                        :model="form"
                        ref="form"
                        label-width="130px"
                        size="small"
                    >
                        <el-row id="topicMove">
                            <el-row
                                class="topicSty"
                                v-for="(item, index) in form.subject"
                                :key="index"
                            >
                                <el-col :span="2" class="centerSty">{{
                                    index + 1
                                }}</el-col>
                                <el-col :span="22">
                                    <el-row>
                                        <el-col :span="16">{{ item.name }}</el-col>
                                        <el-col :span="8" class="rightSty">
                                            <el-button type="text">拖动</el-button>                                         
                                        </el-col>
                                    </el-row>
                                    <el-row
                                        v-for="(it, ind) in item.option"
                                        :key="ind"
                                        v-show="item.type != 3"
                                    >
                                        <el-col :span="16" class="mRsty"
                                            ><el-radio
                                                disabled
                                                v-if="item.type == 1||item.type == 4"
                                            ></el-radio>
                                            <el-checkbox
                                                disabled
                                                v-if="item.type == 2"
                                            ></el-checkbox>
                                            {{ it.option_name }}
                                            <el-row
                                                v-for="(itChild, indChild) in it.option_list"
                                                :key="indChild"
                                                v-show="item.type == 4"
                                                class="childRowTwo"
                                            >
                                                <el-col :span=\'22\'>
                                                    <el-radio
                                                        v-if="it.option_type==2"
                                                        disabled
                                                    ></el-radio>
                                                    <span
                                                        v-if="it.option_type==1"
                                                        >填空:</span>                                            
                                                    {{ itChild.option_name }}
                                                </el-col>
                                            </el-row>
                                        </el-col>
                                    </el-row>
                                    <el-row v-show="item.type == 3">
                                        <el-col :span="16">
                                            <el-input type="textarea"></el-input></el-col>
                                    </el-row>
                                </el-col>
                            </el-row>
                        </el-row>
                    </el-form>
                </el-row>
            </el-bgwhite>
        </div>
    </template>
    
    <script>
    import Sortable from "sortablejs";
    export default {
        data() {
        return {
            form: {
                subject: [],
            },
        };
    },
        mounted() {
            this.rowDrop();
        },
        methods: {
            //行拖拽
            rowDrop() {
                const tbody = document.getElementById("topicMove");
                var that = this;
                Sortable.create(tbody, {
                    sort: true,
                    animation: 300,
                    onEnd: function (evt) {
                        that.form.subject.splice(
                            evt.newIndex,
                            0,
                            that.form.subject.splice(evt.oldIndex, 1)[0]
                        );
                        
                        var newArray = that.form.subject.slice(0);                    
                        let nowId=newArray[evt.newIndex].id;
                        newArray.forEach((item,index)=>{
                            if(item.relationList.length>0){                           
                                for(var i=0;i<item.relationList.length;i++){
                                    if(item.relationList[i].relation_subject==nowId){
                                        item.relationList.splice(i,1)
                                        i--;
                                    }
                                }
                            }
                        })
                        that.form.subject = [];
                        that.$nextTick(function () {
                            that.form.subject = newArray;
                        });
                    },
                });
            },
        },
    };
    </script>
    
    
    
``

 

以上是关于Vue中使用Sortable的主要内容,如果未能解决你的问题,请参考以下文章

vue中使用vue-awesome-swiper

【vue】在vue中使用高德地图API

vue 怎么挂载swiper

在vue中使用iframe标签

在vue中使用checkbox

less在vue中的使用