Appearance
Vue.js 性能优化
组件优化
使用 v-show vs v-if
根据使用场景选择合适的条件渲染指令:
vue
<template>
<div>
<!-- 对于频繁切换的场景使用 v-show -->
<div v-show="isVisible">Frequently toggled content</div>
<!-- 对于条件性渲染且不频繁切换的场景使用 v-if -->
<div v-if="shouldRender">Rarely rendered content</div>
<!-- 当元素数量很多时,考虑使用 v-show -->
<div v-for="item in largeList" :key="item.id">
<div v-show="item.visible">{{ item.name }}</div>
</div>
</div>
</template>
组件懒加载
使用异步组件实现代码分割和懒加载:
javascript
// 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: '/profile',
name: 'Profile',
component: () => import('../views/Profile.vue')
},
{
path: '/admin',
name: 'Admin',
component: () => import('../views/Admin.vue'),
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
使用 keep-alive 缓存组件
vue
<template>
<div id="app">
<nav>
<button @click="currentView = 'Home'">Home</button>
<button @click="currentView = 'About'">About</button>
<button @click="currentView = 'Contact'">Contact</button>
</nav>
<!-- 缓存组件状态 -->
<keep-alive :include="cachedComponents">
<component :is="currentView" />
</keep-alive>
</div>
</template>
<script setup>
import { ref } from 'vue'
import Home from './components/Home.vue'
import About from './components/About.vue'
import Contact from './components/Contact.vue'
const currentView = ref('Home')
const cachedComponents = ref(['Home', 'About']) // 需要缓存的组件
// 动态控制缓存
function toggleCache(componentName) {
if (cachedComponents.value.includes(componentName)) {
cachedComponents.value = cachedComponents.value.filter(c => c !== componentName)
} else {
cachedComponents.value.push(componentName)
}
}
</script>
</template>
渲染优化
使用虚拟滚动处理大量数据
vue
<template>
<div class="virtual-list" ref="containerRef" @scroll="handleScroll">
<div :style="{ height: totalHeight + 'px' }" class="scroll-area">
<div
:style="{
transform: `translateY(${offsetY}px)`,
height: itemHeight + 'px'
}"
class="visible-items"
>
<div
v-for="item in visibleItems"
:key="item.id"
class="list-item"
>
{{ item.content }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
const props = defineProps({
items: Array,
itemHeight: {
type: Number,
default: 50
},
containerHeight: {
type: Number,
default: 400
}
})
const containerRef = ref(null)
const scrollTop = ref(0)
const itemHeight = 50
const containerHeight = 400
// 计算可见项目数量
const visibleCount = computed(() => Math.ceil(containerHeight / itemHeight) + 1)
// 计算起始索引
const startIndex = computed(() => Math.floor(scrollTop.value / itemHeight))
// 计算结束索引
const endIndex = computed(() => Math.min(startIndex.value + visibleCount.value, props.items.length))
// 获取可见项目
const visibleItems = computed(() => {
return props.items.slice(startIndex.value, endIndex.value)
})
// 计算偏移量
const offsetY = computed(() => startIndex.value * itemHeight)
// 计算总高度
const totalHeight = computed(() => props.items.length * itemHeight)
// 处理滚动事件
function handleScroll(event) {
scrollTop.value = event.target.scrollTop
}
</script>
使用 v-memo (Vue 3.2+)
v-memo指令可以缓存子树,避免不必要的重新渲染:
vue
<template>
<div v-for="item in list" :key="item.id">
<!-- 只有当 item.id 或 item.name 改变时才重新渲染 -->
<div v-memo="[item.id, item.name]">
<h3>{{ item.name }}</h3>
<p>{{ item.description }}</p>
<!-- 这部分会根据 memo 条件决定是否重新渲染 -->
<ExpensiveComponent :data="item.data" />
</div>
</div>
</template>
响应式优化
使用 v-once 减少响应式开销
vue
<template>
<div>
<!-- 静态内容使用 v-once,只渲染一次 -->
<div v-once class="header">
<h1>Static Header Content</h1>
<p>This content never changes</p>
</div>
<!-- 动态内容保持响应式 -->
<div class="dynamic-content">
<p>Current time: {{ currentTime }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const currentTime = ref(new Date().toString())
onMounted(() => {
setInterval(() => {
currentTime.value = new Date().toString()
}, 1000)
})
</script>
</template>
优化计算属性
vue
<script setup>
import { computed, reactive } from 'vue'
const state = reactive({
users: [],
filter: '',
sortBy: 'name'
})
// 使用缓存的计算属性
const filteredUsers = computed(() => {
if (!state.filter) return state.users
return state.users.filter(user =>
user.name.toLowerCase().includes(state.filter.toLowerCase())
)
})
// 复杂计算使用计算属性缓存
const sortedUsers = computed(() => {
const users = [...filteredUsers.value] // 创建副本以避免修改原数组
return users.sort((a, b) => {
if (state.sortBy === 'name') {
return a.name.localeCompare(b.name)
} else if (state.sortBy === 'age') {
return a.age - b.age
}
return 0
})
})
</script>
事件处理优化
防抖和节流
vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const searchQuery = ref('')
const searchResults = ref([])
// 防抖搜索函数
let debounceTimer = null
function debouncedSearch(query) {
clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
performSearch(query)
}, 300) // 300ms 防抖延迟
}
async function performSearch(query) {
if (!query.trim()) {
searchResults.value = []
return
}
try {
// 模拟API调用
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`)
searchResults.value = await response.json()
} catch (error) {
console.error('Search failed:', error)
}
}
// 节流滚动处理
let throttleTimer = null
function handleScroll() {
if (throttleTimer) return
throttleTimer = setTimeout(() => {
throttleTimer = null
// 滚动处理逻辑
console.log('Scrolling...')
}, 100) // 100ms 节流间隔
}
onMounted(() => {
window.addEventListener('scroll', handleScroll)
})
onUnmounted(() => {
clearTimeout(debounceTimer)
clearTimeout(throttleTimer)
window.removeEventListener('scroll', handleScroll)
})
</script>
<template>
<div>
<input
v-model="searchQuery"
@input="debouncedSearch(searchQuery)"
placeholder="Search..."
>
<ul>
<li v-for="result in searchResults" :key="result.id">
{{ result.name }}
</li>
</ul>
</div>
</template>
</template>
组件设计优化
使用插槽优化渲染
vue
<!-- Inefficent: 这些组件总是被渲染 -->
<template>
<div>
<expensive-component v-if="showExpensive" />
<another-expensive-component v-if="showAnother" />
</div>
</template>
<!-- Efficient: 使用插槽,只有在使用时才渲染 -->
<template>
<div>
<slot name="expensive-content" v-if="showExpensive"></slot>
<slot name="another-content" v-if="showAnother"></slot>
</div>
</template>
避免不必要的组件嵌套
vue
<!-- 避免过度封装 -->
<template>
<!-- 不好的例子 -->
<div>
<wrapper-component>
<content-component>
<display-component>
<actual-content />
</display-component>
</content-component>
</wrapper-component>
</div>
</template>
<!-- 更好的方式 -->
<template>
<!-- 直接使用必要组件 -->
<div class="content-wrapper">
<div class="content-display">
<actual-content />
</div>
</div>
</template>
生产环境优化
Tree Shaking
确保只导入需要的功能:
javascript
// 好的做法 - 只导入需要的函数
import { ref, computed } from 'vue'
// 避免这种做法 - 导入整个库
import * as Vue from 'vue'
代码分割策略
javascript
// 按功能分割
const routes = [
{
path: '/admin',
component: () => import('./views/AdminPanel.vue'),
children: [
{
path: 'users',
component: () => import('./views/admin/UserManagement.vue')
},
{
path: 'settings',
component: () => import('./views/admin/SystemSettings.vue')
}
]
}
]
性能监控
使用 Vue DevTools
在开发过程中使用Vue DevTools监控组件性能:
vue
<script setup>
import { ref, computed } from 'vue'
// 使用响应式数据时注意性能
const items = ref([])
// 使用计算属性缓存复杂计算
const expensiveValue = computed(() => {
console.time('expensive-computation')
// 复杂计算
const result = items.value
.filter(item => item.active)
.map(item => item.value * 2)
.reduce((sum, val) => sum + val, 0)
console.timeEnd('expensive-computation')
return result
})
</script>
通过这些优化策略,可以显著提升Vue.js应用的性能,提供更好的用户体验。