防抖和節流大家都用過,但 JS 性能優化其實不止這兩種。在日常開發里,有很多實用的方法,可以讓頁面更順暢。
于是我整理了幾個常用的 vue 性能優化寫法,自己用起來也很順手。
1. 避免模板中的重復計算
我以前喜歡直接在模板中寫一些計算邏輯,雖然它能快速實現需求,但如果不小心,可能會影響性能。尤其是當組件頻繁更新時,重復計算的開銷就會顯現出來。
基礎寫法:
<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 屬性,提前計算好需要的數據,避免每次都執行復雜的計算。
<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 都可以控制組件或元素的顯示,但如果用得不對,也會影響性能。
場景示例:
假設有一個選項卡組件,用戶頻繁切換內容:
<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
<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. 懶加載:先渲染用戶可見部分
懶加載是一種非常有效的技術,可以讓頁面內容只有在用戶滾動到該部分時才加載,提升首屏加載速度,減少不必要的請求。
基礎寫法:
<template>
<img :src="imageSrc" />
</template>
<script setup>
const imageSrc = "https://example.com/large-image.jpg"
</script>
問題:直接在組件加載時就請求了圖片,導致首屏加載時間增加。
優化寫法:圖片懶加載
<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")
useIntersectionObserver(imageRef, () => {
imageRef.src = imageSrc.value
})
</script>
優化效果:只有當圖片進入視口時才會開始加載,減少了首屏的資源加載,提高頁面加載速度。
4. 避免過度使用 watch
在 Vue 中,watch 是監聽數據變化的常用方式,但如果用得不當,會導致多次不必要的計算和請求,增加性能開銷。
基礎寫法:
<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 變化時都會觸發請求,尤其是輸入法彈出時,會多次觸發請求。
優化寫法:使用防抖
<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)
})
</script>
優化效果:只有在用戶停止輸入 500 毫秒后才會發起請求,避免了過于頻繁的請求。
5. 避免重復綁定事件
在 Vue3 中,每次渲染組件時,都會重新綁定事件。如果綁定事件的元素很多,性能開銷就會加大,尤其是在動態生成的列表中。
基礎寫法:
<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 都會綁定一個新的事件處理函數,造成不必要的內存開銷。
優化寫法:事件委托
<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>
優化效果:通過事件委托,將事件綁定到父元素上,避免了為每個按鈕都單獨綁定事件,大大減少了內存開銷。