У меня проблема с производительностью v-for
для большого массива.
Проблема в том, что v-for
заново генерирует все отфильтрованные строки каждый раз, когда вы меняете фильтр. Мы решили это, выполнив комбинацию v-show
и v-if
для элемента v-for
. Это сработало отлично, сэкономив ~ 700 мс при перерисовке каждый раз, когда вы фильтруете массив.
Недостатком этого является то, что transition-group
go ??? (бананы)
Вы можете увидеть это в действии в примере ниже, отфильтровав что-то, а затем очистив фильтр. Используя v-show
, элементы делают странный переход из верхнего левого угла. Это не то, что я ожидаю.
new Vue({
el: "#app",
data() {
return {
filter: null,
people: [],
useVshow: true,
settings: {
opacityOnly: false
}
}
},
created() {
/* Irerelevant for issue - Just some fancy example code: */
let arr = [];
for (let i = 0; i < 500; ++i) {
arr.push({
id: i,
name: `person ${i}`,
age: i,
face: Math.floor(Math.random() * 9),
show: true
});
}
this.people = arr;
},
computed: {
filteredPeople: function() {
return this.filter ? this.people.filter(person => person.name.includes(this.filter)) : this.people
}
},
watch: {
filter() {
for (const i of this.people) {
i.show = this.filter ? i.name.includes(this.filter) : true;
}
}
}
})
.wrapper {
position: relative;
}
.card {
transition: all 800ms ease-in-out;
}
.opacity-only {
transition: opacity 800ms ease-in-out;
}
.list-leave-active {
position: absolute;
}
.list-enter,
.list-leave-to {
opacity: 0;
transform: translateY(30px);
}
.container {
max-width: 520px;
margin: 0 auto;
}
/* Irerelevant for issue - Just some fancy example code: */
input {
width: 150px;
}
.card {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 10px 0;
}
.card .info p {
margin: 5px 10px;
}
.card .info .name {
font-weight: 600;
color: #48484c;
}
.card .info .age {
font-size: 14px;
color: #656565;
}
.card .avatar {
height: 80px;
border-radius: 50%;
border: 5px solid #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<main id="app">
<div class="container">
<label for="filter">Filter by name:</label>
<input name="filter" v-model="filter"> Use v-show: <input type="checkbox" v-model="useVshow">
<br>Only trantition opacity: <input type="checkbox" v-model="settings.opacityOnly">
<transition-group name="list" tag="div" class="wrapper">
<template v-if="useVshow">
<div v-for="person in people" v-show="person.show" :key="person.id" class="card" :class="{'opacity-only': settings.opacityOnly}">
<img class="avatar" :src="`https://randomuser.me/api/portraits/lego/${person.face}.jpg`">
<div class="info">
<p class="name">{{person.name}}</p>
<p class="age">{{person.age}} years old</p>
</div>
</div>
</template>
<template v-else>
<div v-for="person in filteredPeople" :key="person.id" class="card">
<img class="avatar" :src="`https://randomuser.me/api/portraits/lego/${person.face}.jpg`">
<div class="info">
<p class="name">{{person.name}}</p>
<p class="age">{{person.age}} years old</p>
</div>
</div>
</template>
</transition-group>
</div>
</main>
Почему это происходит, и есть ли способ сделать возврат-переход таким же, как если бы у вас не было v-show
?
EDIT:
Добавлена опция только для непрозрачности перехода, как указано @Sitethief