概述
在 Vue 项目中,性能优化不仅是技术实现层面的问题,更是影响用户体验的关键因素。页面加载缓慢、交互卡顿、首屏渲染时间过长等问题,都会直接降低用户留存率。本文结合实际项目经验,总结了 Vue 框架中与性能优化相关的核心要点,涵盖常见优化场景、实践技巧与代码实现,并通过详细示例,帮助你打造高性能的 Vue 应用。
1. 组件加载优化
1.1 异步组件加载
// 基础异步组件
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
// 带加载状态的异步组件
const AsyncComponentWithLoading = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
// 在组件中使用
export default {
components: {
AsyncComponent,
AsyncComponentWithLoading
}
}
1.2 路由懒加载
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
},
{
path: '/user/:id',
name: 'User',
component: () => import(
/* webpackChunkName: "user" */
'@/views/User.vue'
)
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
1.3 组件预加载
// 预加载关键组件
const preloadComponents = () => {
// 预加载用户页面组件
import('@/views/User.vue')
// 预加载设置页面组件
import('@/views/Settings.vue')
}
// 在用户交互时预加载
document.addEventListener('mouseover', (e) => {
if (e.target.matches('[data-preload]')) {
preloadComponents()
}
})
2. 渲染性能优化
2.1 v-once指令
<template>
<div>
<!-- 静态内容只渲染一次 -->
<h1 v-once>{{ staticTitle }}</h1>
<!-- 复杂计算只执行一次 -->
<div v-once>
<p>计算结果: {{ expensiveCalculation() }}</p>
</div>
<!-- 动态内容仍然会更新 -->
<p>当前时间: {{ currentTime }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const staticTitle = ref('欢迎来到我的网站')
const currentTime = ref(new Date().toLocaleString())
const expensiveCalculation = () => {
console.log('执行复杂计算')
return Math.random() * 1000
}
let timer
onMounted(() => {
timer = setInterval(() => {
currentTime.value = new Date().toLocaleString()
}, 1000)
})
onUnmounted(() => {
clearInterval(timer)
})
</script>
2.2 v-memo指令
<template>
<div>
<!-- 只有当item.id变化时才重新渲染 -->
<div v-for="item in items" :key="item.id" v-memo="[item.id]">
<h3>{{ item.name }}</h3>
<p>{{ item.description }}</p>
<span>价格: {{ item.price }}</span>
</div>
<!-- 多个依赖项 -->
<div v-memo="[user.id, user.role]">
<p>用户: {{ user.name }}</p>
<p>角色: {{ user.role }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '商品1', description: '描述1', price: 100 },
{ id: 2, name: '商品2', description: '描述2', price: 200 }
])
const user = ref({
id: 1,
name: 'John',
role: 'admin'
})
</script>
2.3 keep-alive缓存
<template>
<div>
<!-- 缓存路由组件 -->
<keep-alive :include="['UserProfile', 'UserSettings']">
<router-view />
</keep-alive>
<!-- 缓存动态组件 -->
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
<!-- 带最大缓存数量限制 -->
<keep-alive :max="10">
<component :is="activeTab" />
</keep-alive>
</div>
</template>
<script setup>
import { ref } from 'vue'
import UserProfile from './UserProfile.vue'
import UserSettings from './UserSettings.vue'
const currentComponent = ref('UserProfile')
const activeTab = ref('tab1')
// 组件切换
const switchComponent = (component) => {
currentComponent.value = component
}
</script>
3. 响应式系统优化
3.1 shallowRef优化
<template>
<div>
<h2>用户列表</h2>
<div v-for="user in users" :key="user.id">
{{ user.name }}
</div>
<button @click="addUser">添加用户</button>
</div>
</template>
<script setup>
import { shallowRef, triggerRef } from 'vue'
// 使用shallowRef避免深度响应式
const users = shallowRef([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
])
const addUser = () => {
// 直接修改数组
users.value.push({ id: 3, name: 'Bob' })
// 手动触发更新
triggerRef(users)
}
// 或者使用新的数组引用
const addUserWithNewRef = () => {
users.value = [...users.value, { id: 3, name: 'Bob' }]
}
</script>
3.2 markRaw优化
<template>
<div>
<canvas ref="canvasRef"></canvas>
<button @click="draw">绘制</button>
</div>
</template>
<script setup>
import { ref, markRaw, onMounted } from 'vue'
const canvasRef = ref(null)
// 标记Canvas上下文为非响应式
let ctx = null
onMounted(() => {
ctx = markRaw(canvasRef.value.getContext('2d'))
})
const draw = () => {
if (ctx) {
ctx.fillStyle = 'red'
ctx.fillRect(10, 10, 100, 100)
}
}
</script>
3.3 readonly优化
<template>
<div>
<h2>配置信息</h2>
<p>应用名称: {{ config.appName }}</p>
<p>版本: {{ config.version }}</p>
<p>环境: {{ config.environment }}</p>
</div>
</template>
<script setup>
import { readonly } from 'vue'
// 只读配置对象
const config = readonly({
appName: 'My App',
version: '1.0.0',
environment: 'production'
})
// 尝试修改会失败(开发环境会警告)
// config.appName = 'New Name' // 无效
</script>
4. 事件处理优化
4.1 防抖节流
<template>
<div>
<input
v-model="searchQuery"
@input="handleSearch"
placeholder="搜索..."
/>
<div @scroll="handleScroll" class="scroll-container">
<!-- 滚动内容 -->
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const searchQuery = ref('')
// 防抖函数
const debounce = (func, delay) => {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
// 节流函数
const throttle = (func, delay) => {
let lastCall = 0
return (...args) => {
const now = Date.now()
if (now - lastCall >= delay) {
lastCall = now
func.apply(this, args)
}
}
}
// 防抖搜索
const handleSearch = debounce((event) => {
console.log('搜索:', event.target.value)
// 执行搜索逻辑
}, 300)
// 节流滚动
const handleScroll = throttle((event) => {
console.log('滚动位置:', event.target.scrollTop)
// 执行滚动逻辑
}, 100)
</script>
4.2 事件委托
<template>
<div @click="handleListClick" class="list-container">
<div
v-for="item in items"
:key="item.id"
:data-id="item.id"
class="list-item"
>
{{ item.name }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' },
{ id: 3, name: '项目3' }
])
// 使用事件委托
const handleListClick = (event) => {
const item = event.target.closest('.list-item')
if (item) {
const id = item.dataset.id
console.log('点击了项目:', id)
// 处理点击逻辑
}
}
</script>
5. 列表渲染优化
5.1 虚拟滚动
<template>
<div class="virtual-list">
<div
class="list-container"
:style="{ height: containerHeight + 'px' }"
@scroll="handleScroll"
>
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
<div
v-for="item in visibleItems"
:key="item.id"
:style="{
position: 'absolute',
top: item.top + 'px',
height: itemHeight + 'px',
width: '100%'
}"
>
{{ item.data.name }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const items = ref([])
const itemHeight = 50
const containerHeight = 400
const scrollTop = ref(0)
// 生成测试数据
onMounted(() => {
items.value = Array.from({ length: 10000 }, (_, i) => ({
id: i,
data: { name: `项目 ${i}` }
}))
})
const totalHeight = computed(() => items.value.length * itemHeight)
const visibleItems = computed(() => {
const start = Math.floor(scrollTop.value / itemHeight)
const end = Math.min(
start + Math.ceil(containerHeight / itemHeight) + 1,
items.value.length
)
return items.value.slice(start, end).map((item, index) => ({
...item,
top: (start + index) * itemHeight
}))
})
const handleScroll = (event) => {
scrollTop.value = event.target.scrollTop
}
</script>
5.2 分页加载
<template>
<div>
<div v-for="item in currentPageItems" :key="item.id">
{{ item.name }}
</div>
<div class="pagination">
<button
@click="prevPage"
:disabled="currentPage === 1"
>
上一页
</button>
<span>{{ currentPage }} / {{ totalPages }}</span>
<button
@click="nextPage"
:disabled="currentPage === totalPages"
>
下一页
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const allItems = ref([])
const currentPage = ref(1)
const pageSize = 20
// 生成测试数据
allItems.value = Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: `项目 ${i}`
}))
const totalPages = computed(() =>
Math.ceil(allItems.value.length / pageSize)
)
const currentPageItems = computed(() => {
const start = (currentPage.value - 1) * pageSize
const end = start + pageSize
return allItems.value.slice(start, end)
})
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--
}
}
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++
}
}
</script>
6. 组件设计优化
6.1 组件拆分
<!-- 大型组件拆分为多个小组件 -->
<template>
<div class="user-dashboard">
<UserHeader :user="user" />
<UserStats :stats="stats" />
<UserActivity :activities="activities" />
<UserSettings :settings="settings" @update="handleSettingsUpdate" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import UserHeader from './UserHeader.vue'
import UserStats from './UserStats.vue'
import UserActivity from './UserActivity.vue'
import UserSettings from './UserSettings.vue'
const user = ref({ name: 'John', email: 'john@example.com' })
const stats = ref({ posts: 10, followers: 100 })
const activities = ref([])
const settings = ref({ theme: 'dark', notifications: true })
const handleSettingsUpdate = (newSettings) => {
settings.value = { ...settings.value, ...newSettings }
}
</script>
6.2 Teleport优化
<!-- 模态框组件 -->
<template>
<Teleport to="body">
<div v-if="isVisible" class="modal-overlay" @click="close">
<div class="modal-content" @click.stop>
<h2>{{ title }}</h2>
<slot></slot>
<button @click="close">关闭</button>
</div>
</div>
</Teleport>
</template>
<script setup>
defineProps({
isVisible: Boolean,
title: String
})
const emit = defineEmits(['close'])
const close = () => {
emit('close')
}
</script>
<!-- 使用模态框 -->
<template>
<div>
<button @click="showModal = true">显示模态框</button>
<Modal :is-visible="showModal" title="确认操作" @close="showModal = false">
<p>确定要执行此操作吗?</p>
</Modal>
</div>
</template>
7. 状态管理优化
7.1 Pinia模块化
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
currentUser: null,
users: [],
loading: false
}),
getters: {
isLoggedIn: (state) => !!state.currentUser,
userCount: (state) => state.users.length
},
actions: {
async fetchUsers() {
this.loading = true
try {
const response = await fetch('/api/users')
this.users = await response.json()
} finally {
this.loading = false
}
}
}
})
// stores/settings.js
export const useSettingsStore = defineStore('settings', {
state: () => ({
theme: 'light',
language: 'zh-CN'
}),
actions: {
updateTheme(theme) {
this.theme = theme
}
}
})
7.2 状态选择器优化
<template>
<div>
<h2>用户信息</h2>
<p v-if="isLoggedIn">欢迎, {{ currentUser.name }}</p>
<p>用户总数: {{ userCount }}</p>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 使用storeToRefs保持响应式
const { currentUser, isLoggedIn, userCount } = storeToRefs(userStore)
// 或者使用computed进行精确选择
import { computed } from 'vue'
const userName = computed(() => userStore.currentUser?.name)
</script>
8. 内存管理优化
8.1 组件销毁清理
<template>
<div>
<h2>定时器组件</h2>
<p>计数: {{ count }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const count = ref(0)
let timer = null
onMounted(() => {
timer = setInterval(() => {
count.value++
}, 1000)
})
onUnmounted(() => {
// 清理定时器
if (timer) {
clearInterval(timer)
timer = null
}
})
</script>
8.2 事件监听器清理
<template>
<div>
<h2>窗口大小: {{ windowSize }}</h2>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const windowSize = ref({ width: 0, height: 0 })
const updateWindowSize = () => {
windowSize.value = {
width: window.innerWidth,
height: window.innerHeight
}
}
onMounted(() => {
updateWindowSize()
window.addEventListener('resize', updateWindowSize)
})
onUnmounted(() => {
// 清理事件监听器
window.removeEventListener('resize', updateWindowSize)
})
</script>
9. 构建优化
9.1 按需引入
// 按需引入Element Plus
import { ElButton, ElInput, ElForm } from 'element-plus'
// 按需引入Lodash
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
// 按需引入工具函数
import { formatDate } from '@/utils/date'
import { validateEmail } from '@/utils/validation'
9.2 代码分割配置
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router', 'pinia'],
ui: ['element-plus'],
utils: ['lodash', 'dayjs']
}
}
}
}
})
10. 性能监控
10.1 性能分析
// utils/performance.js
export const performanceMonitor = {
// 测量函数执行时间
measureTime(name, fn) {
const start = performance.now()
const result = fn()
const end = performance.now()
console.log(`${name} 执行时间: ${end - start}ms`)
return result
},
// 监控组件渲染时间
measureRender(componentName, renderFn) {
return this.measureTime(`${componentName} 渲染`, renderFn)
},
// 监控异步操作
async measureAsync(name, asyncFn) {
const start = performance.now()
const result = await asyncFn()
const end = performance.now()
console.log(`${name} 异步操作时间: ${end - start}ms`)
return result
}
}
10.2 内存使用监控
// utils/memory.js
export const memoryMonitor = {
// 获取内存使用情况
getMemoryUsage() {
if (performance.memory) {
return {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit
}
}
return null
},
// 监控内存泄漏
monitorMemoryLeaks() {
setInterval(() => {
const memory = this.getMemoryUsage()
if (memory) {
const usage = (memory.used / memory.limit) * 100
if (usage > 80) {
console.warn('内存使用率过高:', usage.toFixed(2) + '%')
}
}
}, 5000)
}
}
以上总结了 Vue 项目性能优化的核心注意事项与实践示例代码。合理应用这些优化策略,能够显著提升应用的性能表现与用户体验。建议根据项目实际需求,选择合适的优化方案,并定期开展性能分析与调优,以保持应用的高效运行。

1446

被折叠的 条评论
为什么被折叠?



