如何让 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>