提问人:Damian Chudobiński 提问时间:11/10/2023 最后编辑:Damian Chudobiński 更新时间:11/14/2023 访问量:256
Vue3 with Draggable - 嵌套列表 - 反应性
Vue3 with Draggable - Nesting list - Reactivity
问:
我尝试使用 Draggable 插件制作拖放列表,但取决于我如何从负责嵌套它的组件(第二个代码块)在观察器中评估新值,它只能以一种方式工作
Codesandbox - https://codesandbox.io/s/bold-aj-jwvh93?file=/src/App.vue
主要成分清单
<script setup>
import { ref, watch, reactive, onMounted } from 'vue';
import DraggableListNesting from 'COMPONENT/cms/DraggableListNesting.vue';
const props = defineProps(
{
list:
{
type: Array,
required: true,
default: [],
},
});
const list = ref(props.list);
const emits = defineEmits(
[
'getChildrens',
'updateList',
'listUpdated',
]);
watch(() => props.list, (newList) =>
{
list.value = newList;
},
{
deep: true,
flush: 'post'
});
const childrensUpdated = (args) =>
{
for(let i = 0; i < list.value.length; i++)
{
if(list.value[i].id == args.parentElementId)
{
list.value[i].childrens = args.list;
list.value[i].childrens_count = args.list.length;
}
}
// list.value = Object.assign(args.list, list.value);
};
</script>
<template>
<div>
<DraggableListNesting
v-model="list"
:childrensUpdated="childrensUpdated"
@getChildrens="(args) => emits('getChildrens', args)"
@childrensUpdated="(args) => childrensUpdated(args)"
/>
</div>
</template>
嵌套组件 - DraggableListNesting
<script setup>
import { ref, watch } from 'vue';
import Draggable from 'vuedraggable';
import DraggableItem from 'COMPONENT/cms/DraggableItem.vue';
import DraggableListNesting from 'COMPONENT/cms/DraggableListNesting.vue';
const props = defineProps(
{
modelValue:
{
type: Array,
required: true,
default: [],
},
parentId:
{
type: [Number, Boolean],
required: false,
default: false,
},
childrensUpdated:
{
type: Function,
required: true,
},
});
const emits = defineEmits(
[
'getChildrens',
'childrensUpdated',
]);
const list = ref(props.modelValue);
const parentId = ref(props.parentId);
watch(() => props.modelValue, (newList) =>
{
console.log('list before update by watch');
console.log(list.value);
console.log(newList);
list.value = newList;
// list.value = Object.assign(newList, list.value);
},
{
deep: true,
flush: 'post'
});
</script>
<template>
<Draggable
v-model="list"
tag="ul"
:item-key="item => item.id"
:group="{name: 'edit_list_draggable_nested'}"
@end="(...args) =>
{
emits('childrensUpdated', {list: list, parentElementId: parentId, depth: nestDepth});
}"
>
<template #item="{ element }" :key="element.id">
<div>
{{ element.name }}
</div>
<li :data-draggable-item-id="element.id">
<DraggableListNesting
v-model="element.childrens"
:parentId="element.id"
:childrensUpdated="childrensUpdated"
@getChildrens="(args) => emits('getChildrens', args)"
@childrensUpdated="(args) => childrensUpdated(args)"
></DraggableListNesting>
</li>
</template>
</Draggable>
</template>
list prop 中的一些示例数据
[
{
"id": 16,
"name": "Settings",
"parent_id": null,
"order": 16,
'childrens': [
{
"id": 18,
"name": "Logs",
"parent_id": 16,
"order": 18,
childrens: [],
"childrens_count": 1
},
{
"id": 17,
"name": "Backups",
"parent_id": 16,
"order": 17,
childrens: [],
"childrens_count": 0
}
],
"childrens_count": 2
},
{
"id": 12,
"name": "Analytics",
"parent_id": null,
"order": 12,
childrens: [],
"childrens_count": 0
},
]
因此,为了进行测试,我将“备份”(“设置”的子项)从beign子项拖到主列表中
然后,如果我坚持使用“list.value = newList”
- “备份”已从“设置”正确移动到主列表,但是当观察程序运行本身时,list.value更正了已经修改的列表,但newList获取了没有“备份”的旧列表
- 当我将“分析”移动到其他项目的子项时,一切都很好
如果我尝试执行“list.value = Object.assign(newList, list.value);”(已注释)
- 将“备份”从嵌套到主列表中取消嵌套就可以了
- 但是当我尝试将“Analytics”嵌套为另一个项目的子项时,它唯一的视觉效果是“Setting”的子项(例如),但列表道具中的“Settings”对象没有他作为子项
成功移动后,还需要发送带有父 ID 的 axios 请求,因此必须更新列表值
答:
当你使用 Object.assign(newList, list.value) 时,它会将 newList 中的属性合并到 list.value 中,但它不会用新对象替换 list.value,这可能是 Vue 将其检测为更改所必需的。
在将 newList 分配给 list.value 之前,请考虑创建 newList 的深层副本。这可以使用 JSON 方法 (JSON.parse(JSON.stringify(newList))) 或深层复制实用程序函数来完成。这确保了你正在使用一个新对象,这可以帮助 Vue 的响应式系统检测变化。
更新:
下面是实现一些建议的代码的修改版本:
watch(() => props.list, (newList) => {
Vue.nextTick(() => {
list.value = JSON.parse(JSON.stringify(newList));
});
}, { deep: true, flush: 'post' });
// In your update logic
const childrensUpdated = (args) => {
Vue.set(list.value, args.index, args.newData); // Example of using Vue.set
// Rest of your logic...
};
评论
我不确定我做了什么,我想我只是简化了你的代码。基本上,我认为错误的根本原因是您的代码以某种方式没有正确更新 v-model(就像您使用观察器来更新数据,而数据可能已被 v-model 更新,而 v-model 仅更新组件内部的 ref,但不更新组件父级内部的数据)。
我认为最重要的修复是使用此代码:
<Draggable
:model-value="list"
tag="ul"
:item-key="(item) => item.id"
:group="{ name: 'edit_list_draggable_nested' }"
@update:model-value="(newValue) => emitUpdate(newValue)"
>
以前,它使用 v-model,但现在我使用 v-model expand(和)。然后,emitUpdate 会将数据映射到新数据。:model-value
@update:model-value="(newValue) => emitUpdate(newValue)"
function emitUpdate(newList) {
const updatedList = [...newList].map((nl) => ({
...nl,
parent_id: props.parentId,
}));
list.value = updatedList;
emits("update:modelValue", updatedList);
}
通过这种方式,我们可以在将数据传播到父组件之前对新数据执行某些操作(我们可以分配新的 )。parentId
最后,当每个数据更新时,我们可以这样观察根父级中的数据:
watch(
() => list,
(newList) => {
console.log("list changed", newList.value);
},
{
deep: true,
flush: "post",
}
);
每当发生拖拽事件时,都会触发上面的观察器,我们可以放心地假设 ref 是其最新版本。list
这是分叉的沙盒:
编辑
OP 在评论中指出,它仍然存在一个错误,即渲染的项目与源数据不匹配。我不确定我做了什么,但主要是,我只是将 改为 使用 ,如下所示:element
list[index]
<template #item="{ index }">
...
<DraggableListNesting
v-model="list[index].childrens"
:parentId="list[index].id"
:childrensUpdated="childrensUpdated"
@getChildrens="(args) => emits('getChildrens', args)"
@childrensUpdated="(args) => childrensUpdated(args)"
></DraggableListNesting>
此外,由于某种原因,使用此方法,修复parent_id会重复项目,因此我们可以使用此函数在根目录中递归修复parent_id:
function fixParentIds(newList, id) {
newList.forEach((nl) => {
nl.parent_id = id;
fixParentIds(nl.childrens, nl.id);
});
}
下面是分叉的沙盒:
评论
v-model
watch
评论