防抖和节流大家都用过,但 JS 性能优化其实不止这两种。在日常开发里,有很多实用的方法,可以让页面更顺畅。
于是我整理了几个常用的 vue 性能优化写法,自己用起来也很顺手。
1. 避免模板中的重复计算
我以前喜欢直接在模板中写一些计算逻辑,虽然它能快速实现需求,但如果不小心,可能会影响性能。尤其是当组件频繁更新时,重复计算的开销就会显现出来。
基础写法:
html
<template>
<div v-for="item in list" :key="item.id">
商品:{{ item.name }}
价格:{{ formatPrice(item.price) }}
</div>
</template>
<script setup>
import { ref } from "vue"
const list = ref([
{ id: 1, name: "键盘", price: 1999 },
{ id: 2, name: "鼠标", price: 299 },
{ id: 3, name: "显示器", price: 3999 }
])
const formatPrice = (price) => {
return "¥" + (price / 100).toFixed(2)
}
</script>
问题 :每次渲染时,formatPrice 会重新计算,假如列表有几百个条目,这个性能损耗就会变得明显。
优化写法:
可以使用 Vue3 的 computed 属性,提前计算好需要的数据,避免每次都执行复杂的计算。
html
<template>
<div v-for="item in formattedList" :key="item.id">
商品:{{ item.name }}
价格:{{ item.priceText }}
</div>
</template>
<script setup>
import { ref, computed } from "vue"
const list = ref([
{ id: 1, name: "键盘", price: 1999 },
{ id: 2, name: "鼠标", price: 299 },
{ id: 3, name: "显示器", price: 3999 }
])
const formattedList = computed(() => {
return list.value.map(item => ({
...item,
priceText: "¥" + (item.price / 100).toFixed(2)
}))
})
</script>
优化效果 :只有 list 数据发生变化时,formattedList 才会重新计算,避免了每次渲染时都重新格式化价格,提升了性能。
2. 合理使用 v-if 和 v-show
在 Vue3 中,v-if 和 v-show 都可以控制组件或元素的显示,但如果用得不对,也会影响性能。
场景示例:
假设有一个选项卡组件,用户频繁切换内容:
html
<template>
<button @click="tab = 1">Tab 1</button>
<button @click="tab = 2">Tab 2</button>
<div v-if="tab === 1">内容 1</div>
<div v-if="tab === 2">内容 2</div>
</template>
<script setup>
import { ref } from "vue"
const tab = ref(1)
</script>
问题 :每次切换 tab 时,v-if 都会销毁和重建对应 DOM,如果内容复杂或包含子组件,性能开销明显。
优化写法:使用 v-show
html
<template>
<button @click="tab = 1">Tab 1</button>
<button @click="tab = 2">Tab 2</button>
<div v-show="tab === 1">内容 1</div>
<div v-show="tab === 2">内容 2</div>
</template>
优化效果 :v-show 只是切换元素的 CSS display,不会频繁销毁和重建 DOM,适合频繁切换但数量有限的场景。
如果是初次渲染开销大、切换不频繁,用 v-if;如果是频繁切换,用 v-show。合理选择可以明显提高性能。
3. 懒加载:先渲染用户可见部分
懒加载是一种非常有效的技术,可以让页面内容只有在用户滚动到该部分时才加载,提升首屏加载速度,减少不必要的请求。
基础写法:
html
<template>
<img :src="imageSrc" />
</template>
<script setup>
const imageSrc = "https://example.com/large-image.jpg"
</script>
问题:直接在组件加载时就请求了图片,导致首屏加载时间增加。
优化写法:图片懒加载
html
<template>
<img v-lazy="imageSrc" />
</template>
<script setup>
import { ref } from "vue"
import { useIntersectionObserver } from "@vueuse/core"
const imageSrc = ref("https://example.com/large-image.jpg")
// 使用 @vueuse/core 的 useIntersectionObserver 来懒加载图片
useIntersectionObserver(imageRef, () => {
imageRef.src = imageSrc.value
})
</script>
优化效果:只有当图片进入视口时才会开始加载,减少了首屏的资源加载,提高页面加载速度。
4. 避免过度使用 watch
在 Vue 中,watch 是监听数据变化的常用方式,但如果用得不当,会导致多次不必要的计算和请求,增加性能开销。
基础写法:
html
<template>
<input v-model="searchQuery" placeholder="搜索" />
</template>
<script setup>
import { ref, watch } from "vue"
const searchQuery = ref("")
watch(searchQuery, (newQuery) => {
// 每次输入都会发请求
fetch(`/api/search?q=${newQuery}`)
})
</script>
问题 :每次 searchQuery 变化时都会触发请求,尤其是输入法弹出时,会多次触发请求。
优化写法:使用防抖
html
<template>
<input v-model="searchQuery" placeholder="搜索" />
</template>
<script setup>
import { ref, watch } from "vue"
const searchQuery = ref("")
let timeout
watch(searchQuery, (newQuery) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
fetch(`/api/search?q=${newQuery}`)
}, 500) // 防抖:500ms后才发送请求
})
</script>
优化效果:只有在用户停止输入 500 毫秒后才会发起请求,避免了过于频繁的请求。
5. 避免重复绑定事件
在 Vue3 中,每次渲染组件时,都会重新绑定事件。如果绑定事件的元素很多,性能开销就会加大,尤其是在动态生成的列表中。
基础写法:
html
<template>
<div v-for="item in list" :key="item.id">
<button @click="handleClick(item.id)">点击</button>
</div>
</template>
<script setup>
const handleClick = (id) => {
console.log("点击按钮", id)
}
</script>
问题 :每个 button 都会绑定一个新的事件处理函数,造成不必要的内存开销。
优化写法:事件委托
html
<template>
<div @click="handleClick">
<div v-for="item in list" :key="item.id">
<button>{{ item.name }}</button>
</div>
</div>
</template>
<script setup>
const handleClick = (e) => {
if (e.target.tagName === 'BUTTON') {
console.log('点击按钮', e.target.innerText)
}
}
</script>
优化效果:通过事件委托,将事件绑定到父元素上,避免了为每个按钮都单独绑定事件,大大减少了内存开销。
以上这些写法都是我平时在项目里用的,实用又好操作。如果你在开发中有自己的优化方法,也欢迎在评论区分享,我们一起讨论。