AnnVoV / blog

24 stars 2 forks source link

Vue.Draggable是如何与sortable.js相结合的?启发 #6

Open AnnVoV opened 6 years ago

AnnVoV commented 6 years ago

1.我们只提供容器,那组件是如何渲染的?

​ 当我们使用draggable组件时,我们发现我们只要向下面这样用就可以了, 我们只要把我们需要渲染的东西,作为draggable的子元素即可。那么思考一下,如何实现呢?

<!-- https://github.com/SortableJS/Vue.Draggable -->
<draggable v-model="myArray" :options="{group:'people'}" @start="drag=true" @end="drag=false">
    <ul>
       <li v-for="element in myArray" :key="element.id">{{element.name}}</li>
    </ul>    
</draggable>   

假设我们要实现这个draggable组件, 其实渲染这步挺简单的就是用render函数去做

export default {
    name: 'draggable',
    render(h)=>{
        // 这时候我们肯定没有模板了,只能自己来写
        var slot = this.$slots.default; // 获取默认的子元素vdom
        this.element = slot.tag.toLowerCase();
        // todo 不知道这里attribute具体怎么搞,暂时置为空对象
        // 获取ul 上的一些属性
        // render('标签', {attribute}, children)
        h(this.element, {}, slot[0].children)
    }
}

2.sortbale上的钩子是如何挂到vue组件上并$emit出来的?

​ 我们知道vue.draggable这个组件其实是基于sortable.js 去做拖拽的。里面有个new Sortable(this.$el)的过程。那我先来大致分析下sortable的实现。

​ sortable.js 主要是利用drag的对应事件去做处理(dragstart, dragover, dragend)。

3.sortable 利用了draggable=true这个属性,如何自动为元素设置的?(这是sortable中处理的)

​ 这个其实是在sortable.js里面处理的,作者很鸡贼,他就是先监听tap,然后把对应的target的属性置为draggable=true。(ps.我一开始还以为要遍历子元素,过滤selector, 统一设置为draggable=true)

4.sortable 主要处理了dom相关,dom的移动在sortable里面是通过调用insertBefore去处理的,那我们在组件里绑定的数据是如何进行更新?

​ 这里其实蛮容易想到我们props父元素传入的list是需要更新的,那我们显然需要去使用v-model这个语法糖来最简便的实现数据的双向更新(参考阅读:Vue2 利用 v-model 实现组件props双向绑定的优美解决方案) ;可以发现vue.draggable也确实是这么处理的

// 思路,onMove时,我们通过e.startIndex, e.endIndex修改我们当前的newList, 得到更新后的newList后通过this.$emit('input', newList) 让我们绑定的数据更新
// 我们修改下我们delegateAndEmit方法,让他在$emit时,也能触发我们组建的cb
methods: {
            _delegateAndEmit (evt) {
                return (e) => {
                    // 内置这个组件内部的钩子
                    if (typeof this['onDrag' + evt] === 'function') this['onDrag' + evt](e)
                    this.$emit(evt.toLowerCase(), e)
                }
            },
            onDragMove (e) {
                var newArray = Object.assign([], this.value);
                var startIndex = e.startIndex;
                var endIndex = e.endIndex;
                var temp = newArray.splice(startIndex, 1);
                newArray.splice(endIndex, 0, temp[0]);
                // 所以这里会更新我们的数据,并利用v-model语法糖来双向绑定
                this.$emit('input', newArray);
            }
        }

完整的两个文件会放到 /js/myDrag/ 文件夹下。