类别过滤器无限循环警告 Vue JS

Category Filter Infinite Loop Warning Vue JS

我有一个应用程序可以根据程序和类型过滤事件。应用程序按预期运行,但我收到错误消息“您可能在组件渲染函数中有一个无限更新循环”。我认为问题可能出在我的 noEvents computed 属性 但我不确定如何解决。

// List of events

const events = [{
        month: 'January',
        monthEvents: [{
                type: 'Type 1',
                program: 'Program 2',
                visible: true
            },
            {
                type: 'Type 2',
                program: 'Program 1',
                visible: true
            },
            {
                type: 'Type 2',
                program: 'Program 2',
                visible: true
            },
            {
                type: 'Type 1',
                program: 'Program 4',
                visible: true
            }
        ]
    },
    {
        month: 'February',
        monthEvents: [{
                type: 'Type 4',
                program: 'Program 2',
                visible: true
            },
            {
                type: 'Type 2',
                program: 'Program 1',
                visible: true
            },
            {
                type: 'Type 3',
                program: 'Program 3',
                visible: true
            },
            {
                type: 'Type 3',
                program: 'Program 4',
                visible: true
            }
        ]
    }
]

new Vue({
    el: '#app',
    data: {
        monthCounter: 0,
        allEvents: events,
        filteredTypes: [],
        filteredPrograms: []
    },
    methods: {
        increaseMonthCounter: function () {
            // Increases the month counter by 1
            this.filteredTypes = [];
            this.filteredPrograms = [];
                this.monthCounter++;
            if (this.monthCounter > this.allEvents.length - 1) {
                this.monthCounter = 0;
            }
        },
        decreaseMonthCounter: function () {
            // Decreases the month counter by 1
            this.filteredTypes = [];
            this.filteredPrograms = [];
                this.monthCounter--;
            if (this.monthCounter == -1) {
                this.monthCounter = this.allEvents.length - 1;
            }
        },
        filterEvents: function (event) {
            //Loops through each event and checks multiple conditions to display or hide event.

            // If there is nothing filtered show all events
            if (this.filteredTypes.length === 0 && this.filteredPrograms.length === 0) {
                event.visible = true;
                return true;
            }

            // If a type is selected and no program is selected show all types
            if (this.filteredTypes.includes(event.type) && this.filteredPrograms.length === 0) {
                event.visible = true;
                return true;
            } else {
                event.visible = false;
            }

            // If a program is selected and no type is selected show all programs
            if (this.filteredPrograms.includes(event.program) && this.filteredTypes.length === 0) {
                event.visible = true;
                return true;
            } else {
                event.visible = false;
            }

            // If selection matches events type and program show event

            if (this.filteredPrograms.includes(event.program) && this.filteredTypes.includes(event.type)) {
                event.visible = true;
                return true;
            } else {
                event.visible = false;
            }

        }
    },
    computed: {
        currentMonth: function () {
            //Gets the currrent month
            return events[this.monthCounter].month;
        },
        availableMonthEvents: function () {
            // Gets the current month's events
            return events[this.monthCounter].monthEvents;
        },
        noEvents: function () {
            // Checks if there are no events available
            for (let i = 0; i < this.availableMonthEvents.length; i++){
                if (this.availableMonthEvents[i].visible == true) {
                    return false;
                }
            }

            return true; 
        },
        availableTypes: function () {
            // List out the available types and strips out the duplicates
            const availableTypes = [];
            events[this.monthCounter].monthEvents.forEach((item) => {
                if (!availableTypes.includes(item.type)) {
                    availableTypes.push(item.type);
                }
            });
            return availableTypes;
        },
        availablePrograms: function () {
            // List out the available programs and strips out the duplicates 
            const availablePrograms = [];
            events[this.monthCounter].monthEvents.forEach((item) => {
                if (!availablePrograms.includes(item.program)) {
                    availablePrograms.push(item.program);
                }
            });
            return availablePrograms;
        }
    }
});
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">

    <title>Hello, world!</title>
  </head>
  <body>
    <div class="container my-5">
        <div id="app">
            <h3>{{currentMonth}}</h3>
            <strong>Types:</strong>
            <div class="form-check form-check-inline" v-for="type in availableTypes">
                <input class="form-check-input" type="checkbox" id="inlineCheckbox1" :value="type" v-model="filteredTypes">
                <label class="form-check-label" for="inlineCheckbox1">{{type}}</label>
            </div>
            <br>
            <strong>Programs:</strong>
            <div class="form-check form-check-inline" v-for="program in availablePrograms">
                <input class="form-check-input" type="checkbox" id="inlineCheckbox1" :value="program" v-model="filteredPrograms">
                <label class="form-check-label" for="inlineCheckbox1">{{program}}</label>
            </div>
            <p class="mt-3">
                <button class="btn btn-danger" @click="decreaseMonthCounter()">Down</button>
                <button class="btn btn-success" @click="increaseMonthCounter()">Up</button>
            </p>
            <ol class="mt-3" id="event-container">
                <li class="event" v-if="filterEvents(event)" v-for="event in availableMonthEvents">
                    <strong>Type:</strong> {{event.type}} - <strong>Program:</strong> {{event.program}}
                </li>
                <li v-if="noEvents">
                    There are no events!
                </li>
            </ol>
        </div>
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="app.js"></script>
  </body>
</html>

我认为问题可能出在您的 filterEvents 方法以及您如何在页面上呈现事件

v-ifv-for 等两个指令不应放在单个 html 元素上。 我认为 Vue 试图做的是:

尝试呈现事件 => 看到 v-if 指令 => 运行 filterEvents => 修改事件 => 尝试再次呈现事件(因为事件是数组的一部分,所以数组被认为已修改)= > ...

不要改变 v-if 检查中的任何数据。

可能的解决方案

实施 filteredEvents 计算 属性,这将 return 数组有条件地过滤。

这些文档可以提供帮助:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

我能够通过使用计算的属性过滤我的列表来解决我的问题。

// List of events

const events = [{
        month: 'January',
        monthEvents: [{
                type: 'Type 1',
                program: 'Program 2'
            },
            {
                type: 'Type 2',
                program: 'Program 1'
            },
            {
                type: 'Type 2',
                program: 'Program 2'
            },
            {
                type: 'Type 1',
                program: 'Program 4'
            }
        ]
    },
    {
        month: 'February',
        monthEvents: [{
                type: 'Type 4',
                program: 'Program 2'
            },
            {
                type: 'Type 2',
                program: 'Program 1'
            },
            {
                type: 'Type 3',
                program: 'Program 3'
            },
            {
                type: 'Type 3',
                program: 'Program 4'
            }
        ]
    }
]

new Vue({
    el: '#app',
    data: {
        monthCounter: 0,
        allEvents: events,
        filteredTypes: [],
        filteredPrograms: []
    },
    methods: {
        increaseMonthCounter: function () {
            // Increases the month counter by 1
            this.filteredTypes = [];
            this.filteredPrograms = [];
                this.monthCounter++;
            if (this.monthCounter > this.allEvents.length - 1) {
                this.monthCounter = 0;
            }
        },
        decreaseMonthCounter: function () {
            // Decreases the month counter by 1
            this.filteredTypes = [];
            this.filteredPrograms = [];
                this.monthCounter--;
            if (this.monthCounter == -1) {
                this.monthCounter = this.allEvents.length - 1;
            }
        }
    },
    computed: {
        currentMonth: function () {
            //Gets the currrent month
            return events[this.monthCounter].month;
        },
        availableMonthEvents: function () {
            // Gets the current month's events
            return events[this.monthCounter].monthEvents;
        },
        filteredEvents: function () {

            let filteredEvents = this.availableMonthEvents.filter((item) => {
                // If there is nothing filtered show all events
            if (this.filteredTypes.length === 0 && this.filteredPrograms.length === 0) {
                return true;
            }

            // If a type is selected and no program is selected show all types
            if (this.filteredTypes.includes(item.type) && this.filteredPrograms.length === 0) {
                return true;
            }

            // If a program is selected and no type is selected show all programs
            if (this.filteredPrograms.includes(item.program) && this.filteredTypes.length === 0) {
                return true;
            }

            // If selection matches events type and program show event

            if (this.filteredPrograms.includes(item.program) && this.filteredTypes.includes(item.type)) {
                return true;
            }
            });

            return filteredEvents;
        },
        availableTypes: function () {
            // List out the available types and strips out the duplicates
            const availableTypes = [];
            events[this.monthCounter].monthEvents.forEach((item) => {
                if (!availableTypes.includes(item.type)) {
                    availableTypes.push(item.type);
                }
            });
            return availableTypes;
        },
        availablePrograms: function () {
            // List out the available programs and strips out the duplicates 
            const availablePrograms = [];
            events[this.monthCounter].monthEvents.forEach((item) => {
                if (!availablePrograms.includes(item.program)) {
                    availablePrograms.push(item.program);
                }
            });
            return availablePrograms;
        }
    }
});
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">

    <title>Hello, world!</title>
  </head>
  <body>
    <div class="container my-5">
        <div id="app">
            <h3>{{currentMonth}}</h3>
            <strong>Types:</strong>
            <div class="form-check form-check-inline" v-for="type in availableTypes">
                <input class="form-check-input" type="checkbox" id="inlineCheckbox1" :value="type" v-model="filteredTypes">
                <label class="form-check-label" for="inlineCheckbox1">{{type}}</label>
            </div>
            <br>
            <strong>Programs:</strong>
            <div class="form-check form-check-inline" v-for="program in availablePrograms">
                <input class="form-check-input" type="checkbox" id="inlineCheckbox1" :value="program" v-model="filteredPrograms">
                <label class="form-check-label" for="inlineCheckbox1">{{program}}</label>
            </div>
            <p class="mt-3">
                <button class="btn btn-danger" @click="decreaseMonthCounter()">Down</button>
                <button class="btn btn-success" @click="increaseMonthCounter()">Up</button>
            </p>
            <ol class="mt-3">
                <li class="event" v-for="event in filteredEvents">
                    <strong>Type:</strong> {{event.type}} - <strong>Program:</strong> {{event.program}}
                </li>
                <li v-if="filteredEvents.length == 0">
                    There are no events!
                </li>
            </ol>
        </div>
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="app.js"></script>
  </body>
</html>