likes
comments
collection
share

🔥想不到吧!v-for使用不当会触发不必要的更新导致性能问题

作者站长头像
站长
· 阅读数 7

前言

大家好,对于vue项目来说,v-for那真是随处可见,不知道大家平时有没有注意到,在使用v-for时,修改列表某一项数据,导致的整个列表数据重新计算产生的性能问题。我自己倒还真没注意过,对于一些列表数据比较少的可能影响较小,但对于一些长列表存在比较复杂的计算的话,还是会存在较大的性能消耗。下文将通过一个例子说明问题,并据此提出了一些解决方案。

背景

最近一个朋友向我发出的这个问题,问我有没有遇到过这个问题。具体的例子就是在做购物车时,当增加购物车中的某一个商品的购买数量时,会触发整个购物车中的所有商品的小计价格(购买数量*商品价格)都重新计算了一遍。想要解决的问题是:“能不能只触发修改的那个商品的计算,其他商品跳过更新。”

v-for触发整个列表计算方法重新执行问题示例

下面我们就用这个简单的购物车案例,说明一下这个问题,页面及代码如下:

🔥想不到吧!v-for使用不当会触发不必要的更新导致性能问题

order.vue

<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()小计计算方法。但是,很遗憾,点击按钮后,你会发现控制台输出把所有商品的小计都重新计算了一遍!

🔥想不到吧!v-for使用不当会触发不必要的更新导致性能问题

下面我们说下解决办法!

封装Item组件优化

我们把单个商品元素封装成一个组件引用,使用v-for来渲染Item组件

Item.vue

<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

<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>
...省略以下前面写到的代码

🔥想不到吧!v-for使用不当会触发不必要的更新导致性能问题

这时,我们再点击按钮使可乐+1时,由上图可看出,只触发了可乐对应的Item组件下的小计方法。

由此我们也可得出一个结论,在一些使用v-for渲染的列表中,视图依赖data触发更新的例子(比如:购物车、点赞、收藏),封装成一个Item组件还是很有必要的,单独引用的组件,才能做到差量数据更新,否则会造成整个列表数据重新计算。试想一下,如果渲染一个十万条数据的列表,修改其中一项导致整个列表重新计算,其中将会导致巨大的性能消耗。

v-memo优化

除了封装Item组件外,在vue.js@3.2+版本中,其实也新出了一个内置指令v-memo,可以优化以上问题,达到和封装Item组件那样的效果,这里我从vue官网截了个图简单介绍一下,想要了解更多关于v-memo的介绍,可自行查阅官网。废话不多说,我们下面使用v-memo指令解决以上问题。

🔥想不到吧!v-for使用不当会触发不必要的更新导致性能问题

其实用法很简单,只需在v-for的元素上增加v-memo="[item.amount]",当数组里面的item.amount改变时,只会触发对应item的视图更新,没有改变的item则会跳过所有更新,甚至虚拟 DOMvnode 创建也将被跳过,因为缓存的子树副本可以被重新使用。

<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>

我们点击双倍快乐按钮,打印如下图所示,也实现了差量更视图的效果。

🔥想不到吧!v-for使用不当会触发不必要的更新导致性能问题

总结

vue项目开发,使用v-for进行列表渲染时,可根据业务需求看是否需要操作列表数组更新视图,确定是否需要封装组件单独引用,如有相关操作,从性能优化的角度看,建议封装成一个Item组件,单独引入使用,以实现差量数据更新的目的。如果渲染列表数据量比较大,且项目使用的是vue3.2+版本,也可考虑使用v-memo的优化手段来手动避免节点出现不需要的更新

参考文献

uniapp官网-优化长列表数据更新

vue官方文档-v-memo指令

转载自:https://juejin.cn/post/7345379924167180329
评论
请登录