将 v-model 与 groupBy 数组一起使用,return 平面数组

Use v-model with groupBy array, return flat array

我正在尝试设置一个 Vue 组件,该组件采用数组中项目的平面列表,按 属性 将它们分组以用于子组件,并发出更新的平面数组。

我的部分组件在它们的 v 模型中使用这些分组的项目并发出更新后的列表。部分组件是一个带有一些输入字段的拖放,因此项目在部分组件下发生更改并发出更新的列表。

下面是一个以平面列表为prop的组件示例:

<template>
    <div>
        <div v-for="section in template.sections" :key="section.id">
            <h2>{{ section.name }}</h2>
            <item-section :section="section" v-model="sectionData[section.id]"></item-section>
        </div>
    </div>
</template>

<script type="text/javascript">
import { groupBy } from "lodash";
import ItemSection from "@/components/Section.vue";

export default {
    name: "ItemAssignment",
    props: {
        // All items in flat array
        value: {
            type: Array,
            required: true,
            default: () => [
                /**
                 * {
                 *  id: null,
                 *  section_id: null,
                 *  name: null  
                 * }
                 */
            ]
        },
        // Template (containing available sections)
        template: {
            type: Object,
            default: () => {
                return {
                    sections: [
                        /**
                         * {
                         *  id: null,
                         *  name: null  
                         * }
                         */             
                    ]
                };
            }
        }
    },
    components: {
        ItemSection
    },
    data() {
        return {
            sectionData: []
        };
    },
    mounted() {},
    computed: {
        flattenedData() {
            return Object.values(this.sectionData).flat();
        }
    },
    methods: {},
    watch: {
        // Flat list updated
        value: {
            immediate: true,
            deep: true,
            handler(val) {
                this.sectionData = groupBy(val, "section_id");
            }
        },
        // --- Causing infinite loop ---
        // flattenedData(val) {
        //  this.$emit("input", val);
        // },
    }
};
</script>

这个组件的父级基本上是这样的:

<template>
    <div>
        <!-- List items should be updatable here or from within the assignment component -->
        <item-assignment v-model="listItems"></item-assignment>
    </div>
</template>

<script type="text/javascript">
import ItemAssignment from "@/components/ItemAssignment.vue";

export default {
    name: "ItemExample",
    props: {

    },
    components: {
        ItemAssignment
    },
    data() {
        return {
            listItems: []
        };
    },
    mounted() {},
    computed: {

    },
    methods: {
        // Coming from API...
        importExisting(list) {
            var newList = [];

            list.forEach(item => {
                const newItem = {
                    id: null, // New record, so don't inherit ID
                    section_id: item.section_id,
                    name: item.name
                };

                newList.push(newItem);
            });

            this.listItems = newList;
        }
    },
    watch: {

    }
};
</script>

发出最终平面数组时,Vue 进入无限循环尝试重新处理列表,浏览器选项卡冻结。

我相信 groupBy and/or Object.values(array).flat() 方法正在剥离反应性,因此 Vue 不断认为它是不同的数据,因此无限循环。

我试过手动遍历项目并将它们推送到临时数组,但遇到了同样的问题。

如果有人知道在保持反应性的同时对这些项目进行分组和展平的方法,我将不胜感激。谢谢!

所以发生这种情况是有道理的...

groupBy 函数创建了一个新数组,由于您正在观察该数组,因此触发了 input 事件,这导致父级更新并传递相同的值,该值在循环中再次触发。

由于您已经在使用 lodash,您可以包含 isEqual 比较数组的函数

import { groupBy, isEqual } from "lodash";
import ItemSection from "@/components/Section.vue";

export default {
// ...redacted code...
    watch: {
        // Flat list updated
        value: {
            immediate: true,
            deep: true,
            handler(val, oldVal) {
                if (!isEqual(val, oldVal))
                    this.sectionData = groupBy(val, "section_id");
            }
        },
        flattenedData(val) {
           this.$emit("input", val);
        },
    }
};

如果新旧值相同,这应该可以防止 this.sectionData 更新。

这也可以在 flattenedData 中完成,但需要另一个值来存储之前的状态。