Vue3 with Draggable - 嵌套列表 - 反应性

Vue3 with Draggable - Nesting list - Reactivity

提问人:Damian Chudobiński 提问时间:11/10/2023 最后编辑:Damian Chudobiński 更新时间:11/14/2023 访问量:256

问:

我尝试使用 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 请求,因此必须更新列表值

vue.js vuejs3 可拖拽 vue-composition-api vuedraggable

评论

0赞 Damzaky 11/12/2023
你能用它做一个代码沙盒吗?
0赞 Damian Chudobiński 11/12/2023
我已经花了一些时间 codesandbox.io/p/sandbox/......但它看起来不起作用,我在一些博客上读到的是 codesandbox 中尚未完全支持组合 api,但官方什么都没有
1赞 Damzaky 11/12/2023
嗯,我认为你应该从模板中制作,而不是那样(我认为你上面制作的是从其中有“Cloud”标签的模板中制作的),尝试寻找没有“Cloud”标签的模板
0赞 Damian Chudobiński 11/12/2023
我在清除模板 codesandbox.io/s/bold-aj-jwvh93?file=/vue.config.js 时添加了@Damzaky

答:

0赞 berkobienb 11/13/2023 #1

当你使用 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...
};

评论

1赞 Damian Chudobiński 11/13/2023
这可能会解决另一个我已经没有遇到的问题,但对于我面临的问题,它并没有解决它
0赞 Damian Chudobiński 11/13/2023
在任何情况下都不能使用 Vue.set - 我的意思是它不起作用,只有互联网上的东西我看到所有说 set 已被弃用,refs 或响应式应该这样做 jobe
1赞 Damzaky 11/13/2023 #2

我不确定我做了什么,我想我只是简化了你的代码。基本上,我认为错误的根本原因是您的代码以某种方式没有正确更新 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

这是分叉的沙盒:

编辑 bold-ellis-m3jn54

编辑

OP 在评论中指出,它仍然存在一个错误,即渲染的项目与源数据不匹配。我不确定我做了什么,但主要是,我只是将 改为 使用 ,如下所示:elementlist[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);
  });
}

下面是分叉的沙盒:

编辑 nostalgic-shamir-4dk8cr

评论

0赞 Damian Chudobiński 11/14/2023
这并不完全有效,它与我存档的案例之一非常相似 要检查这是否正常工作,您只需在名称旁边显示 childrens 属性的长度 <span v-if=“element.childrens”>[{{ element.childrens.length }}]</span>{{ element.name }} 而不是 {{ element.name }}
0赞 Damzaky 11/14/2023
@DamianChudobiński请检查我的编辑
0赞 Damian Chudobiński 11/15/2023
如果我确实喜欢编辑部分,当我取消嵌套项目时,它会作为子项目在取消嵌套之前成为他的兄弟姐妹
0赞 Damzaky 11/15/2023
@DamianChudobiński确定您彻底遵循了编辑?就像你必须检查 s、、等。我的意思是它不会发生在沙盒中,对吧?如果您已根据我的编辑进行了编辑,但仍有问题,则可能是您没有正确进行编辑,或者您的代码中有其他内容导致您未将其包含在沙盒中v-modelwatch
0赞 Damian Chudobiński 11/19/2023
这在 DraggableList 中非常接近,我还必须在观察程序中克隆 newList ''' watch(() => list, (newList) => { if(newList) { let cloneList = JSON.parse(JSON.stringify(newList)); fixParentIds(cloneList, null); list.value = cloneList; } }, { deep: true, flush: 'post' });```