Vue 3 with Draggable -嵌套列表- Reactivity

dojqjjoe  于 7个月前  发布在  Vue.js
关注(0)|答案(2)|浏览(196)

我试图使拖放列表与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>


列表属性中的一些示例数据

[
    {
        "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
    },
]


因此,对于测试,我将“Backups”(“Settings”的子项)从beign子项拖到主列表中
如果我坚持使用“list.value = newList”

  • “备份”正确地从“设置”移动到主列表,但当观察者运行自己的列表.值有正确的已经修改的列表,但newList得到的旧列表没有“备份”还没有
  • 当我移动“分析”是一个其他项目的孩子它的所有罚款

如果我尝试执行“list.value = Object.assign(newList,list.value);”(注解掉),

  • 将“Backups”从嵌套中移除到主列表中也没问题
  • 但是,当我尝试嵌套“分析”作为另一个项目的孩子,它唯一的视觉添加到“设置”的孩子(例如),但“设置”对象在列表 prop 没有他作为孩子

移动成功后,我还需要发送Axios请求与父ID,所以列表值必须更新

oogrdqng

oogrdqng1#

我不知道我做了什么,我想我只是简化了你的代码。基本上,我认为bug的根本原因是你的代码不知何故没有正确更新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扩展(:model-value@update:model-value="(newValue) => emitUpdate(newValue)")。然后,emitUpdate将数据Map到新数据。

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",
  }
);


每当拖动事件发生时,上面的监视器就会被触发,我们可以安全地假设list ref是它的最新版本。
这是分叉沙盒:
x1c 0d1x的数据

编辑

OP在评论中指出,它仍然有一个bug,渲染的项目与源数据不匹配。我不确定我做了什么,但主要是,我只是将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);
  });
}


以下是分叉沙箱:


hsvhsicv

hsvhsicv2#

当你使用Object.assign(newList,list.value)时,它会将newList中的属性合并到list.value中,但不会用新对象替换list.value,这可能是Vue检测到更改所必需的。
在将newList赋值给list.value之前,可以考虑创建newList的深度副本。这可以使用JSON方法(JSON.parse(JSON.stringify(newList)))或深度副本实用函数来完成。这可以确保您使用的是一个新鲜的对象,这可以帮助Vue的React系统检测更改。
更新:
下面是代码的修改版本,实现了一些建议:

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...
};

字符串

相关问题