如何让 Vue 响应式内容快速更新?
How to make Vue reactive content update quickly?
我有一个问题,Vue 在更新反应性内容时反应迟缓。
我希望用户 select 列表中的项目。当项目被 selected 时,他们应该被标记。为了进行测试,我只是在 selected 项目周围设置了一个边框。问题是当我有多个项目时,我觉得 Vue 需要很长时间来更新(反应)selected 项目上的 class。
所以我有一个简单的反应式商店,如下所示:
export default new Vuex.Store({
state: {
selections: []
},
mutations: {
set_selections (state, sel) {
state.selections = sel;
}
}
})
我将这个商店传递给我的组件,该组件呈现一个包含许多项目的简单列表。
<p v-for="item in items">
<span v-bind:class="{ 'is-selected': isSelected(item) }" v-on:click="onSelect(item)">
{{ item.name }}
</span>
</p>
所以每个项目都会有一个唯一的 ID,我 add/remove 来自我的 Vuex 商店状态,selections:
onSelect: function(item, event){
let itemId = item._id;
let sel = this.selections;
if (sel.indexOf(itemId) !== -1) {
var index = sel.indexOf(itemId);
sel.splice(index, 1);
} else {
sel.push(itemId);
}
this.$store.commit("set_selections", sel);
},
其中,
selections: function() {
return this.$store.state.selections;
}
是计算得到的 属性,它获取当前的 select 离子。
检查项目是否被 selected 并因此将“is-selected”class 添加到 DOM 元素的方法,如下所示如下:
isSelected: function(item){
let itemId = item._id;
let sel = this.selections;
if (sel.indexOf(itemId) !== -1) {
return true;
}
return false;
},
问题
当我的列表中有很多项目时,我觉得反应性内容非常缓慢。当我点击一个项目时,它需要大约 500 毫秒到 1 秒才能标记该项目。 (注意我有很多项目)。是,我可能做错了什么?由于我循环使用 v-for
,我知道 Vue 必须为每个可能耗时的项目重新计算 isSelected
方法。
当然我可以 add/remove class 直接在 onClick 事件上,但是我失去了使用 Vue 的全部意义。你会如何处理这个问题?
我认为您的列表更新很慢,因为如果您以千 components/items.
为单位迭代 selection 数组可能会很昂贵
也如评论中所述key
绑定也可以提高速度(速度差异未测试)。
但在我的演示中,最好的结果是在创建一个 selection 对象并检查 selected
中的 属性 之后。
而且最好按照计算结果进行 selected
检查 属性 - 它更快。
对于计算 属性 来说,需要大约 200 毫秒才能 select 包含 1000 个项目的列表。使用 selection 方法需要大约 450 毫秒 - 不确定为什么会这么慢。
这是一个 selection 事件 200 毫秒的性能截图(在 Chrome 59.0.3071.115(64 位)- i5-6200 / 8GB RAM / Win10 中测试):
目前,这是我最快的版本。我还从 0.5 到 1 秒开始显示 selection。
请查看下面的演示或此 fiddle。
const listItem = {
props: ['item'],
template: `
<li :class="{ 'is-selected': selected }" @click="$emit('selected', item)">
{{ item.name }}
</li>
`,
computed: {
...Vuex.mapState(['selections']),
selected () {
// 200ms time to mark item for click with 1000 list items - clicked Test 326
return this.selections[this.item.id] !== undefined; // object property check is fast
}
},
methods: {
selected () {
// 450ms to mark selection
//console.log('selected', this.selections, !!this.selections[this.item.id]);
// slightly slower than computed property
return this.selections[this.item.id] !== undefined; // object property check is fast
// array --> slow because another iteration for each component required
//this.selections.indexOf(this.item) !== -1
}
}
};
const list = {
props: ['items'],
template: `
<ul>
<list-item
v-for="item in items"
:item="item"
@selected="select"
:key="item.id"></list-item>
</ul>
`,
components: {
listItem
},
methods: {
select(item) {
this.$store.commit('set_selection', item)
}
}
};
const store = new Vuex.Store({
state: {
selections: []
},
mutations: {
set_selection (state, item) {
//state.selections = sel;
console.log('clicked', item, state.selections[item.id]);
if (state.selections[item.id]) {
// in object --> remove from selection
//let {[item.id]: deleted, ...newState} = state;
state.selections[item.id] = undefined;
}
else {
// not in object --> add item
state.selections = {
...state.selections,
[item.id]: item
}
}
// console.log(state.selections, !!state.selections[item.id]);
/*
--> array approach is slow
if (state.selections.indexOf(item) === -1)
{
// not in list --> push to list
state.selections.push(item);
}
else {
// in list --> remove selection
state.selections.pop(item);
}*/
}
}
})
function createItems(count) {
let items = [];
for(let i=0; i< count; i++) {
items.push({
id: i,
name: 'Test ' + i
});
}
return items;
}
new Vue({
el: '#app',
store,
data () {
let items = createItems(1000);
return {
items
};
},
components: {
list
}
})
.is-selected {
background-color: red;
border: 1px solid red;
}
ul {
list-style-type: none;
}
li {
cursor: pointer;
}
li:hover {
border: 1px solid gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<list :items="items"></list>
</div>
我有一个问题,Vue 在更新反应性内容时反应迟缓。
我希望用户 select 列表中的项目。当项目被 selected 时,他们应该被标记。为了进行测试,我只是在 selected 项目周围设置了一个边框。问题是当我有多个项目时,我觉得 Vue 需要很长时间来更新(反应)selected 项目上的 class。
所以我有一个简单的反应式商店,如下所示:
export default new Vuex.Store({
state: {
selections: []
},
mutations: {
set_selections (state, sel) {
state.selections = sel;
}
}
})
我将这个商店传递给我的组件,该组件呈现一个包含许多项目的简单列表。
<p v-for="item in items">
<span v-bind:class="{ 'is-selected': isSelected(item) }" v-on:click="onSelect(item)">
{{ item.name }}
</span>
</p>
所以每个项目都会有一个唯一的 ID,我 add/remove 来自我的 Vuex 商店状态,selections:
onSelect: function(item, event){
let itemId = item._id;
let sel = this.selections;
if (sel.indexOf(itemId) !== -1) {
var index = sel.indexOf(itemId);
sel.splice(index, 1);
} else {
sel.push(itemId);
}
this.$store.commit("set_selections", sel);
},
其中,
selections: function() {
return this.$store.state.selections;
}
是计算得到的 属性,它获取当前的 select 离子。
检查项目是否被 selected 并因此将“is-selected”class 添加到 DOM 元素的方法,如下所示如下:
isSelected: function(item){
let itemId = item._id;
let sel = this.selections;
if (sel.indexOf(itemId) !== -1) {
return true;
}
return false;
},
问题
当我的列表中有很多项目时,我觉得反应性内容非常缓慢。当我点击一个项目时,它需要大约 500 毫秒到 1 秒才能标记该项目。 (注意我有很多项目)。是,我可能做错了什么?由于我循环使用 v-for
,我知道 Vue 必须为每个可能耗时的项目重新计算 isSelected
方法。
当然我可以 add/remove class 直接在 onClick 事件上,但是我失去了使用 Vue 的全部意义。你会如何处理这个问题?
我认为您的列表更新很慢,因为如果您以千 components/items.
为单位迭代 selection 数组可能会很昂贵也如评论中所述key
绑定也可以提高速度(速度差异未测试)。
但在我的演示中,最好的结果是在创建一个 selection 对象并检查 selected
中的 属性 之后。
而且最好按照计算结果进行 selected
检查 属性 - 它更快。
对于计算 属性 来说,需要大约 200 毫秒才能 select 包含 1000 个项目的列表。使用 selection 方法需要大约 450 毫秒 - 不确定为什么会这么慢。
这是一个 selection 事件 200 毫秒的性能截图(在 Chrome 59.0.3071.115(64 位)- i5-6200 / 8GB RAM / Win10 中测试):
目前,这是我最快的版本。我还从 0.5 到 1 秒开始显示 selection。
请查看下面的演示或此 fiddle。
const listItem = {
props: ['item'],
template: `
<li :class="{ 'is-selected': selected }" @click="$emit('selected', item)">
{{ item.name }}
</li>
`,
computed: {
...Vuex.mapState(['selections']),
selected () {
// 200ms time to mark item for click with 1000 list items - clicked Test 326
return this.selections[this.item.id] !== undefined; // object property check is fast
}
},
methods: {
selected () {
// 450ms to mark selection
//console.log('selected', this.selections, !!this.selections[this.item.id]);
// slightly slower than computed property
return this.selections[this.item.id] !== undefined; // object property check is fast
// array --> slow because another iteration for each component required
//this.selections.indexOf(this.item) !== -1
}
}
};
const list = {
props: ['items'],
template: `
<ul>
<list-item
v-for="item in items"
:item="item"
@selected="select"
:key="item.id"></list-item>
</ul>
`,
components: {
listItem
},
methods: {
select(item) {
this.$store.commit('set_selection', item)
}
}
};
const store = new Vuex.Store({
state: {
selections: []
},
mutations: {
set_selection (state, item) {
//state.selections = sel;
console.log('clicked', item, state.selections[item.id]);
if (state.selections[item.id]) {
// in object --> remove from selection
//let {[item.id]: deleted, ...newState} = state;
state.selections[item.id] = undefined;
}
else {
// not in object --> add item
state.selections = {
...state.selections,
[item.id]: item
}
}
// console.log(state.selections, !!state.selections[item.id]);
/*
--> array approach is slow
if (state.selections.indexOf(item) === -1)
{
// not in list --> push to list
state.selections.push(item);
}
else {
// in list --> remove selection
state.selections.pop(item);
}*/
}
}
})
function createItems(count) {
let items = [];
for(let i=0; i< count; i++) {
items.push({
id: i,
name: 'Test ' + i
});
}
return items;
}
new Vue({
el: '#app',
store,
data () {
let items = createItems(1000);
return {
items
};
},
components: {
list
}
})
.is-selected {
background-color: red;
border: 1px solid red;
}
ul {
list-style-type: none;
}
li {
cursor: pointer;
}
li:hover {
border: 1px solid gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<list :items="items"></list>
</div>