我有这个使用组合API的Vue3组件:
<script setup lang="ts">
import { reactive } from "vue";
import draggable from "vuedraggable"
const myArray = reactive([
{ name : "AAA", id : 1},
{ name : "BBB", id : 2},
{ name : "CCC", id : 3},
{ name : "DDD", id : 4}
])
</script>
<template>
<draggable
:list="myArray"
@end="endDrag"
item-key="id">
<template #item="{element}">
<div>{{element.name}}</div>
</template>
</draggable>
</template>
这是有效的,在endDrag
例程中,我看到myArray
被重新排列。但是,如果我将:list
更改为v-model
,则屏幕上的列表不会更改,并且拖动的元素会返回到其原始位置。并不是说myArray
没有改变。
Bug或功能?我做错什么了?谢谢你的启发!
我刚刚遇到了同样的问题。让我和大家分享我的理解。
首先,让我们确定一些事实。
众所周知,v-model
基本上是这样的:
<Component :modelValue="myObject" @update:modelValue="(newObject) => myObject = newObject/>
如果使用脚本设置,则声明的const和其他声明将成为对象的属性。
所以这个:
const myObject = reactive({
id: 'App',
});
变成这样:
export default {
setup() {
const myObject = reactive({
id: 'App',
});
return {
myObject,
};
},
}
现在让我们把这些知识放在一起。当子组件发出带有新对象的事件update:modelValue
时,Vue所做的是对安装程序返回的对象进行赋值。伪码中:setupObject.myObject = newObjectFromEvent
你可以通过这个简单的代码来证明这一点:
<script>
import { isProxy, reactive } from 'vue';
import Test from './Test';
export default {
components: {
Test
},
setup() {
const myObject = reactive({
id: 'App',
});
const setupObject = {
myObject,
};
setTimeout(() => {
console.log(setupObject.myObject);
console.log(isProxy(setupObject.myObject));
}, 5000);
return setupObject;
},
}
</script>
<template>
<Test v-model="myObject"/>
</template>
<script setup>
import { defineProps, isRef, watch } from 'vue';
const props = defineProps(['modelValue']);
watch(props.modelValue, () => {
console.log('Value changed!');
});
</script>
<template>
<button @click="$emit('update:modelValue', { id: 'Test' })">Click me</button>
</template>
点击按钮后,您会看到我们的console.log
将输出:{id: 'Test'}
和false
。
此外,测试组件的观察程序也不会被触发,因为我们的属性不再是被动的,并且我们的测试组件仍然持有myObject的旧值
同样的事情也发生在可拖动的v型上。拖放项目时,属性(在您的情况下为myArray
(将被可拖动组件发出的非反应对象覆盖,该属性将成为完成设置代码时创建的较大对象的一部分,因此您不会看到项目位置发生更改。
你可能会问,为什么它能和裁判一起工作?好吧,回到我们的@update:modelValue="(newObject) => myObject = newObject
,如果你使用ref,Proxy不会消失,因为赋值是对myObject.value
属性完成的。因此,当你使用ref时,它已经开始工作了。
使用reactive
的另一种方法是手动处理更新:
const handleUpdate = (newObject) => {
Object.assign(myObject, newObject);
};
<Test :modelValue="myObject" @update:modelValue="handleUpdate"/>
这样你就不会失去反应性,因为你保留了相同的代理,只是重新分配了属性。
底线是,在使用具有反应对象或数组的v-model时,必须非常谨慎。