likes
comments
collection
share

VUE Draggable拖拽组件拖拽失效问题

作者站长头像
站长
· 阅读数 73

引入问题

在使用vue拖拽组件时,遇到了一个非常反常规的问题。 首先我使用了reactive包装了一个数组,数组中是对象数据,然后将这个reactive响应式对象传递给拖拽组件,希望能通过这个组件实现一系列拖拽业务功能。

const myData = reactive([
    {"aaa":123,"id":1},
    {"aaa":234,"id":2}
    ]);

<draggable v-model="myData" element="'ul'" itemKey="element=>element.id">
                <template #item="{element,index}">
                    <div>{{element.aaa}}</div>
                </template>
</draggable>

感觉是没什么问题,数据也能正常加载出来,但是在拖拽时发现,能拖的动,但是放手后数据会回到原来的位置。

经过搜索,发现解决这个问题的方法很简单,就是 reactive换成ref 就可以了。

但是这个原因却让人无法理解,非常的违反思维常规。

在学习vue的时候,就经常听到一句话“使用普通变量时,建议用ref,而使用对象或数据时要使用reactive”,这也是因为ref只能对浅层数据变化触发响应式更新,例如const a = ref([]),当使用a.value.push(123)等方法修改列表数据时,watch是监听不到这个改变的,或者说列表或对象元素内容的变化是无法触发响应式更新的,而也就是建议使用reactive的原因,因为reactive能对深层数据变化进行响应更新。

ref和reactive机制差异问题

关于这个问题,在使用搜索引擎、gpt等工具查找搜索后,发现了一种有意思的解释。这个解释说到“ref和reactive的数据更新和获取方式有差异,ref使用 Proxy 实现,可以检测更广泛的操作,如 .value 赋值等。而reactive,则是使用getter/setter实现,无法通过.value进行赋值操作”

所以其实可以猜测,在使用reactive时,可能是数据变更时无法使用.value进行数据修改,导致赋值失败,从而无法顺利进行新数据的渲染。

拖拽组件返回的数据问题

我们姑且认为上面这个说法是正确的,但是这又引发了一个问题。如果是因为某些机制差异,必须要使用ref,那么ref只能响应到浅层变化或引用发生变化,它又是如何能够在列表某个局部数据发生顺序变化时发生响应呢,我们修改局部数据,按说应该是属于深层数据修改。

那么很有可能,拖拽组件基于这个因素,其实 并没有在原数据上做修改,而是返回了一个新的数组,这样一来也就使得引用发生变更,触发了响应式更新

经过阅读拖拽组件的源代码,也确实是这样的:

onDragUpdate(evt) {
      removeNode(evt.item);
      insertNodeAt(evt.from, evt.item, evt.oldIndex);
      const oldIndex = this.context.index; // 获取原来的位置索引
      const newIndex = this.getVmIndex(evt.newIndex); // 获取新的位置索引
      this.updatePosition(oldIndex, newIndex); // 触发列表更新操作
      const moved = { element: this.context.element, oldIndex, newIndex };
      this.emitChanges({ moved });
    },
   
updatePosition(oldIndex, newIndex) {
  		// 这个updatePosition是一个箭头函数
 			// 这个函数会根据原数组、元素原始位置、新位置进行调整,并返回该调整后的数组
      const updatePosition = list =>
        list.splice(newIndex, 0, list.splice(oldIndex, 1)[0]);
  			// 这里使用了list.splice(newIndex, 0, newElement)的方式插入数据,第二个参数为0表示插入模式
  			// 首先list.splice(oldIndex, 1)会删除旧位置元素内容
  			// 返回该数据会,通过list.splice(oldIndex, 1)[0]可以获取到该元素
  			// list.splice(newIndex, 0, list.splice(oldIndex, 1)[0]);返回了这个数组的更新顺序后的呢绒
      this.alterList(updatePosition); //这里是直接传了一个函数到alterList函数中
    },

alterList(onList) {
  		// 这里的onList就是updatePosition(list),注意list是一个函数参数
  		// 这个函数中会对dragable的数据传入方式进行判断
  	  // 如果传入dragable的是list,那么会直接在这个list上修改并返回
      if (this.list) {
        onList(this.list);
        return;
      }
  		// 而如果传入的是value,则会先展开value,创建一个list,修改后并返回
  		// 根据官方文档中对value和list参数的描述,在使用v-model时是value而非list
      const newList = [...this.value];
      onList(newList);
      this.$emit("input", newList);
    },

list: {
    type: Array,
    required: false,
    default: null
  },
  value: {
    type: Array,
    required: false,
    default: null
  },
    
/*
使用v-model时,是用的value而非list:

value
Type: Array
Required: false
Default: null
Input array to draggable component. Typically same array as referenced by inner element v-for directive.
This is the preferred way to use Vue.draggable as it is compatible with Vuex.
It should not be used directly but only though the v-model directive:
<draggable v-model="myArray">


list
Type: Array
Required: false
Default: null
Alternative to the value prop, list is an array to be synchronized with drag-and-drop.
The main difference is that list prop is updated by draggable component using splice method, whereas value is immutable.
Do not use in conjunction with value prop.
*/

通过上述代码分析我们可以得知

(1) 在使用v-model传入数据到dragable组件时,数据将传入到value而非list属性中

(2) 这时在拖拽发生数据改变时,沿着其函数调用栈,会发现最终其实是创建了一个新的listconst newList = [...this.value],并通过onList(newList);在这个新的list上修改了数据的顺序

(3) 最终会通过this.$emit("input", newList);返回结果给父组件。

因此父组件接收到的其实是一个新的list数据,因此在使用ref时,相当于是修改了数据的引用,从而能够顺利触发响应式变化。

至于是否是由于“reactive通过set来设置数据,而ref是通过.value来设置数据,从而导致reactive无法正常使用”的问题,通过源代码可以看到这个新的列表newList,会通过一个名称为input的事件往外抛给父组件,由父组件进行处理。父组件中处理的这部分源代码我暂时没精力去阅读,有兴趣的可以去看一看

转载自:https://juejin.cn/post/7242129428510883896
评论
请登录