【问题标题】:Use v-model with groupBy array, return flat array将 v-model 与 groupBy 数组一起使用,返回平面数组
【发布时间】:2019-12-21 08:42:28
【问题描述】:

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

我的部分组件在其 v-model 中使用这些分组的项目并发出更新的列表。部分组件是带有一些输入字段的拖放,因此在部分组件下更改项目并发出更新的列表。

这是一个将平面列表作为道具的组件示例:

<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 和/或 Object.values(array).flat() 方法正在剥离反应性,因此 Vue 一直认为它是不同的数据,因此是无限循环。

我尝试手动循环遍历这些项目并将它们推送到一个临时数组,但遇到了同样的问题。

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

【问题讨论】:

  • 你试过将 v-model 分解为 value 和 @input 吗?因为这基本上就是 v-model 的组成部分。
  • 是的。尝试将 :value 和 @input 用于进行展平的方法,但是一旦在此组件中发出新的展平数组,就会出现相同的问题。你的意思是在这个的父组件上尝试这个?
  • 所以我想你可以将 item-section 的值绑定到一个计算属性(这是你的平面数组的分组版本)。在 item-section 的输入上,使用一种方法来更新平面数组。这样你就可以删除平面数组上的手表了
  • 是导致循环的$emit 还是父组件的监听器?可以分享一下父组件对ItemAssignment的使用吗?
  • 感谢您的建议。如果我使用计算设置器,它不会在更新时设置。我似乎需要一个深度观察者来检测重新排序和项目值更新。每当我向这个组件传递一个新列表时,我都会得到恒定的输入/输出循环。

标签: vue.js vuejs2 v-model


【解决方案1】:

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

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 中完成,但需要另一个值来存储之前的状态。

【讨论】:

  • 非常感谢!使用isEqual 就像一个魅力!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-14
  • 2021-12-30
  • 2016-02-18
  • 2018-10-02
  • 2020-11-16
  • 2020-01-03
  • 2020-05-01
相关资源
最近更新 更多