前言
大家好,对于vue
项目来说,v-for
那真是随处可见,不知道大家平时有没有注意到,在使用v-for
时导致的整个列表计算重载产生的性能问题。我自己倒还真没注意过,对于一些列表数据比较少的可能影响较小,但对于一些长列表存在比较复杂的计算的话,还是会存在较大的性能消耗。下文将通过一个例子说明问题,并据此提出了一些解决方案。
背景
最近一个朋友向我发出的这个问题,问我有没有遇到过这个问题。具体的例子 就是在做购物车 时,当增加购物车中的某一个商品的购买数量 时,会触发整个购物车列表的重载,导致购物车中的所有商品的价格都重新计算了一遍。想要解决的问题是:"能不能只触发修改的那个商品的计算,其他商品跳过更新。"
v-for触发整个列表数据重载问题示例
下面我们就用这个简单的购物车案例,说明一下这个问题,页面及代码如下:
order.vue
js
<template>
<div class="app">
<div class="cart">
<div class="item" v-for="item in list" :key="item.id">
<span>名称:{{ item.name }}</span>------
<span>购买数量:{{ item.amount }}</span>------
<span>价格:{{ item.price }}</span>------
<span>小计:{{ subPrice(item.price, item.amount) }}</span>
</div>
</div>
<button @click="change">双倍快乐</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 购物车清单
const list = ref([
{ id: 'a', name: '可乐', amount: 1, price: 2 },
{ id: 'b', name: '炸鸡', amount: 2, price: 3 },
{ id: 'c', name: '汉堡', amount: 3, price: 4 },
{ id: 'd', name: '薯条', amount: 4, price: 5 }
])
// 计算小计
const subPrice = (price, amount) => {
console.log(price, amount)
return price * amount
}
// 可乐数量+1
const change = () => {
list.value[0].amount++
}
</script>
当我们点击双倍快乐按钮 时,使可乐数量+1
,我们期望的效果是只更新可乐节点的小计 ,其他节点则不需要触发subPrice()
小计计算方法更新视图。但是,很遗憾,点击按钮后,你会发现控制台输出把所有商品的小计都重新计算了一遍!
下面我们说下解决办法!
封装Item组件优化
我们把单个商品元素封装成一个组件引用,使用v-for
来渲染Item
组件
Item.vue
js
<template>
<div class="item">
<span>名称:{{ data.name }}</span>------
<span>购买数量:{{ data.amount }}</span>------
<span>价格:{{ data.price }}</span>------
<span>小计:{{ subPrice(data.price, data.amount) }}</span>
</div>
</template>
<script setup>
defineProps(['data'])
const subPrice = (price, amount) => {
console.log(price, amount)
return price * amount
}
</script>
order.vue
js
<template>
<div class="app">
<div class="cart">
<Item v-for="item in list" :key="item.id" :data="item"></Item>
</div>
<button @click="change">双倍快乐</button>
</div>
</template>
...省略以下前面写到的代码
这时,我们再点击按钮使可乐+1 时,由上图可看出,只触发了可乐 对应的Item
组件下的小计方法。
由此我们也可得出一个结论,在一些使用v-for
渲染的列表中,视图依赖data
触发更新的例子(比如:购物车、点赞、收藏 ),封装成一个Item
组件还是很有必要的,单独引用的组件,才能做到差量数据更新 ,否则会造成整个列表数据重载。试想一下,如果渲染一个十万条数据的列表,修改其中一项导致整个列表重载,其中将会导致巨大的性能消耗。
v-memo优化
除了封装Item
组件外,在vue.js@3.2+
版本中,其实也新出了一个内置指令v-memo,可以优化以上问题,达到和封装Item
组件那样的效果,这里我从vue
官网截了个图简单介绍一下,想要了解更多关于v-memo的介绍,可自行查阅官网。废话不多说,我们下面使用v-memo
指令解决以上问题。
其实用法很简单,只需在v-for
的元素上增加v-memo="[item.amount]"
,当数组里面的item.amount
改变时,只会触发对应item
的视图更新,没有改变的item
则会跳过更新。
js
<template>
<div class="app">
<div class="cart">
<div class="item" v-for="item in list" :key="item.id" v-memo="[item.amount]">
<span>名称:{{ item.name }}</span>------
<span>购买数量:{{ item.amount }}</span>------
<span>价格:{{ item.price }}</span>------
<span>小计:{{ subPrice(item.price, item.amount) }}</span>
</div>
</div>
<button @click="change">双倍快乐</button>
</div>
</template>
我们点击双倍快乐 按钮,打印如下图所示,也实现了差量更视图的效果。
总结
做vue
项目开发,使用v-for
进行列表渲染时,可根据业务需求看是否需要操作列表数组更新视图(比如:增加商品数量、点赞、收藏等)确定是否需要封装组件单独引用,如有相关操作,从性能优化的角度看,建议封装成一个Item
组件,单独引入使用,以实现差量数据更新 的目的。如果渲染列表数据量比较大,且项目使用的是vue3.2+
版本,也可考虑使用v-memo
的优化手段来解决整个列表数据重载问题。