性能优化
性能优化是 Vue.js 应用开发中的重要环节。本指南提供了全面的性能优化策略和最佳实践。
性能分析工具
Vue DevTools
javascript
// 在开发环境中启用性能分析
if (process.env.NODE_ENV === 'development') {
app.config.performance = true
}
浏览器性能工具
javascript
// 使用 Performance API 测量性能
function measurePerformance(name, fn) {
performance.mark(`${name}-start`)
const result = fn()
performance.mark(`${name}-end`)
performance.measure(name, `${name}-start`, `${name}-end`)
const measure = performance.getEntriesByName(name)[0]
console.log(`${name} took ${measure.duration}ms`)
return result
}
// 使用示例
const result = measurePerformance('data-processing', () => {
return processLargeDataSet(data)
})
自定义性能监控
javascript
// composables/usePerformanceMonitor.js
import { ref, onMounted, onUnmounted } from 'vue'
export function usePerformanceMonitor(componentName) {
const renderTime = ref(0)
const mountTime = ref(0)
let startTime = 0
onMounted(() => {
const endTime = performance.now()
mountTime.value = endTime - startTime
console.log(`${componentName} mounted in ${mountTime.value}ms`)
})
function startRender() {
startTime = performance.now()
}
function endRender() {
renderTime.value = performance.now() - startTime
console.log(`${componentName} rendered in ${renderTime.value}ms`)
}
return {
renderTime,
mountTime,
startRender,
endRender
}
}
组件优化
组件懒加载
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(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue'),
children: [
{
path: 'analytics',
component: () => import('../views/dashboard/Analytics.vue')
},
{
path: 'reports',
component: () => import('../views/dashboard/Reports.vue')
}
]
}
]
export default createRouter({
history: createWebHistory(),
routes
})
异步组件
vue
<template>
<div>
<h1>主页面</h1>
<!-- 条件加载重型组件 -->
<Suspense v-if="showChart">
<template #default>
<AsyncChart :data="chartData" />
</template>
<template #fallback>
<div class="loading">加载图表中...</div>
</template>
</Suspense>
<!-- 延迟加载组件 -->
<LazyComponent v-if="shouldLoadComponent" />
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
// 异步组件定义
const AsyncChart = defineAsyncComponent({
loader: () => import('./components/Chart.vue'),
loadingComponent: () => import('./components/Loading.vue'),
errorComponent: () => import('./components/Error.vue'),
delay: 200,
timeout: 3000
})
// 懒加载组件
const LazyComponent = defineAsyncComponent(() =>
import('./components/LazyComponent.vue')
)
const showChart = ref(false)
const shouldLoadComponent = ref(false)
const chartData = ref([])
// 延迟加载逻辑
setTimeout(() => {
shouldLoadComponent.value = true
}, 2000)
</script>
组件缓存
vue
<template>
<div>
<!-- 缓存动态组件 -->
<KeepAlive :include="cachedComponents" :max="10">
<component :is="currentComponent" :key="componentKey" />
</KeepAlive>
<!-- 缓存路由组件 -->
<router-view v-slot="{ Component, route }">
<KeepAlive :include="['UserProfile', 'ProductList']">
<component :is="Component" :key="route.fullPath" />
</KeepAlive>
</router-view>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const currentComponent = ref('ComponentA')
const componentKey = ref(0)
// 动态控制缓存的组件
const cachedComponents = computed(() => {
const components = ['ComponentA', 'ComponentB']
// 根据条件决定是否缓存
if (someCondition.value) {
components.push('ComponentC')
}
return components
})
// 强制刷新组件
function refreshComponent() {
componentKey.value++
}
</script>
虚拟滚动
vue
<template>
<div class="virtual-list" ref="containerRef" @scroll="handleScroll">
<div class="virtual-list__phantom" :style="{ height: totalHeight + 'px' }"></div>
<div class="virtual-list__content" :style="{ transform: `translateY(${offsetY}px)` }">
<div
v-for="item in visibleItems"
:key="item.id"
class="virtual-list__item"
:style="{ height: itemHeight + 'px' }"
>
<slot :item="item" :index="item.index">
{{ item.text }}
</slot>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
const props = defineProps({
items: {
type: Array,
required: true
},
itemHeight: {
type: Number,
default: 50
},
containerHeight: {
type: Number,
default: 400
}
})
const containerRef = ref(null)
const scrollTop = ref(0)
// 计算总高度
const totalHeight = computed(() => props.items.length * props.itemHeight)
// 计算可见区域的起始和结束索引
const startIndex = computed(() => {
return Math.floor(scrollTop.value / props.itemHeight)
})
const endIndex = computed(() => {
const visibleCount = Math.ceil(props.containerHeight / props.itemHeight)
return Math.min(startIndex.value + visibleCount + 1, props.items.length)
})
// 计算可见项目
const visibleItems = computed(() => {
return props.items.slice(startIndex.value, endIndex.value).map((item, index) => ({
...item,
index: startIndex.value + index
}))
})
// 计算偏移量
const offsetY = computed(() => startIndex.value * props.itemHeight)
// 处理滚动事件
function handleScroll(event) {
scrollTop.value = event.target.scrollTop
}
// 滚动到指定项目
function scrollToItem(index) {
if (containerRef.value) {
containerRef.value.scrollTop = index * props.itemHeight
}
}
defineExpose({
scrollToItem
})
</script>
<style scoped>
.virtual-list {
height: v-bind('containerHeight + "px"');
overflow-y: auto;
position: relative;
}
.virtual-list__phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.virtual-list__content {
position: absolute;
left: 0;
right: 0;
top: 0;
}
.virtual-list__item {
padding: 10px;
border-bottom: 1px solid #eee;
}
</style>
响应式优化
合理使用响应式 API
javascript
// ✅ 使用 shallowRef 优化大对象
const largeDataSet = shallowRef({
users: new Array(10000).fill(null).map((_, i) => ({ id: i, name: `User ${i}` })),
products: new Array(5000).fill(null).map((_, i) => ({ id: i, name: `Product ${i}` }))
})
// ✅ 使用 shallowReactive 优化大数组
const items = shallowReactive([])
// ✅ 使用 markRaw 标记非响应式对象
const chart = markRaw(new Chart(canvas, config))
const map = markRaw(new GoogleMap(element, options))
// ✅ 使用 readonly 保护数据
const readonlyConfig = readonly({
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
})
// ✅ 避免不必要的响应式转换
function processData(rawData) {
// 如果数据只用于计算,不需要响应式
const processedData = rawData.map(item => ({
...item,
processed: true
}))
// 只有最终结果需要响应式
return ref(processedData.filter(item => item.active))
}
计算属性优化
javascript
// ✅ 使用计算属性缓存昂贵的计算
const expensiveValue = computed(() => {
console.log('Computing expensive value...')
return items.value.reduce((sum, item) => {
return sum + item.price * item.quantity
}, 0)
})
// ✅ 分解复杂的计算属性
const filteredItems = computed(() => {
return items.value.filter(item => item.category === selectedCategory.value)
})
const sortedItems = computed(() => {
return [...filteredItems.value].sort((a, b) => {
return sortOrder.value === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)
})
})
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return sortedItems.value.slice(start, end)
})
// ❌ 避免在计算属性中进行副作用操作
const badComputed = computed(() => {
// 不要在计算属性中修改其他状态
someOtherState.value = 'modified' // ❌
// 不要在计算属性中进行 API 调用
fetchData() // ❌
return someValue.value * 2
})
侦听器优化
javascript
// ✅ 使用 watchEffect 自动收集依赖
watchEffect(() => {
if (user.value && user.value.id) {
fetchUserData(user.value.id)
}
})
// ✅ 使用 watch 精确控制依赖
watch(
() => [searchText.value, selectedCategory.value],
([newSearchText, newCategory], [oldSearchText, oldCategory]) => {
if (newSearchText !== oldSearchText || newCategory !== oldCategory) {
debouncedSearch(newSearchText, newCategory)
}
},
{ deep: false } // 避免不必要的深度监听
)
// ✅ 使用防抖优化频繁更新
import { debounce } from 'lodash-es'
const debouncedSearch = debounce((searchText, category) => {
performSearch(searchText, category)
}, 300)
// ✅ 清理侦听器
const stopWatcher = watch(someRef, (newVal) => {
// 处理逻辑
})
// 在适当的时候停止侦听
onUnmounted(() => {
stopWatcher()
})
渲染优化
模板优化
vue
<template>
<!-- ✅ 使用 v-memo 优化列表渲染 -->
<div
v-for="item in expensiveList"
:key="item.id"
v-memo="[item.id, item.selected, item.updated]"
class="list-item"
>
<ExpensiveComponent :item="item" />
</div>
<!-- ✅ 使用 v-once 优化静态内容 -->
<div v-once>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
<!-- ✅ 使用 v-show 而不是 v-if(频繁切换) -->
<div v-show="isVisible" class="toggle-content">
频繁切换的内容
</div>
<!-- ✅ 使用 key 强制重新渲染 -->
<UserForm :key="user.id" :user="user" />
<!-- ✅ 避免在模板中使用复杂表达式 -->
<div>{{ formattedDate }}</div>
<!-- ❌ 避免这样做 -->
<!-- <div>{{ new Date(timestamp).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }) }}</div> -->
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['timestamp', 'user', 'expensiveList'])
// 将复杂逻辑提取到计算属性
const formattedDate = computed(() => {
return new Date(props.timestamp).toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
})
</script>
事件优化
vue
<template>
<!-- ✅ 使用事件委托 -->
<div @click="handleContainerClick">
<button data-action="edit" :data-id="item.id">编辑</button>
<button data-action="delete" :data-id="item.id">删除</button>
<button data-action="view" :data-id="item.id">查看</button>
</div>
<!-- ✅ 使用修饰符优化事件处理 -->
<form @submit.prevent="handleSubmit">
<input @keyup.enter="handleEnter" @input.trim="handleInput">
<button type="submit">提交</button>
</form>
<!-- ✅ 防抖处理频繁事件 -->
<input @input="debouncedInput" placeholder="搜索...">
<!-- ✅ 使用 passive 修饰符优化滚动性能 -->
<div @scroll.passive="handleScroll" class="scroll-container">
<!-- 滚动内容 -->
</div>
</template>
<script setup>
import { debounce } from 'lodash-es'
// 事件委托处理
function handleContainerClick(event) {
const { target } = event
const action = target.dataset.action
const id = target.dataset.id
if (action && id) {
switch (action) {
case 'edit':
editItem(id)
break
case 'delete':
deleteItem(id)
break
case 'view':
viewItem(id)
break
}
}
}
// 防抖输入处理
const debouncedInput = debounce((event) => {
performSearch(event.target.value)
}, 300)
// 节流滚动处理
const throttledScroll = throttle((event) => {
updateScrollPosition(event.target.scrollTop)
}, 100)
</script>
网络优化
API 请求优化
javascript
// composables/useApi.js
import { ref, computed } from 'vue'
import axios from 'axios'
// 请求缓存
const cache = new Map()
// 请求去重
const pendingRequests = new Map()
export function useApi() {
const loading = ref(false)
const error = ref(null)
// 带缓存的请求
async function request(url, options = {}) {
const cacheKey = `${url}${JSON.stringify(options)}`
// 检查缓存
if (cache.has(cacheKey) && !options.force) {
return cache.get(cacheKey)
}
// 检查是否有相同的请求正在进行
if (pendingRequests.has(cacheKey)) {
return pendingRequests.get(cacheKey)
}
loading.value = true
error.value = null
const requestPromise = axios(url, options)
.then(response => {
const data = response.data
// 缓存结果
cache.set(cacheKey, data)
return data
})
.catch(err => {
error.value = err
throw err
})
.finally(() => {
loading.value = false
pendingRequests.delete(cacheKey)
})
pendingRequests.set(cacheKey, requestPromise)
return requestPromise
}
// 批量请求
async function batchRequest(requests) {
loading.value = true
error.value = null
try {
const results = await Promise.allSettled(
requests.map(req => request(req.url, req.options))
)
return results.map(result => {
if (result.status === 'fulfilled') {
return result.value
} else {
console.error('Request failed:', result.reason)
return null
}
})
} finally {
loading.value = false
}
}
// 清除缓存
function clearCache(pattern) {
if (pattern) {
for (const key of cache.keys()) {
if (key.includes(pattern)) {
cache.delete(key)
}
}
} else {
cache.clear()
}
}
return {
loading: computed(() => loading.value),
error: computed(() => error.value),
request,
batchRequest,
clearCache
}
}
数据预加载
javascript
// composables/usePreload.js
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
export function usePreload() {
const router = useRouter()
const preloadedData = ref(new Map())
// 预加载路由组件
function preloadRoute(routeName) {
const route = router.resolve({ name: routeName })
if (route.matched.length > 0) {
const component = route.matched[0].components?.default
if (typeof component === 'function') {
component() // 触发动态导入
}
}
}
// 预加载数据
async function preloadData(key, fetcher) {
if (!preloadedData.value.has(key)) {
try {
const data = await fetcher()
preloadedData.value.set(key, data)
} catch (error) {
console.error(`Failed to preload data for ${key}:`, error)
}
}
}
// 获取预加载的数据
function getPreloadedData(key) {
return preloadedData.value.get(key)
}
// 在鼠标悬停时预加载
function onHoverPreload(routeName) {
let timeoutId
return {
onMouseEnter() {
timeoutId = setTimeout(() => {
preloadRoute(routeName)
}, 100) // 100ms 延迟,避免误触
},
onMouseLeave() {
if (timeoutId) {
clearTimeout(timeoutId)
}
}
}
}
return {
preloadRoute,
preloadData,
getPreloadedData,
onHoverPreload
}
}
图片优化
vue
<template>
<!-- 懒加载图片 -->
<img
v-for="image in images"
:key="image.id"
v-lazy="image.src"
:alt="image.alt"
class="lazy-image"
>
<!-- 响应式图片 -->
<picture>
<source media="(min-width: 768px)" :srcset="image.desktop">
<source media="(min-width: 480px)" :srcset="image.tablet">
<img :src="image.mobile" :alt="image.alt">
</picture>
<!-- WebP 支持 -->
<picture>
<source type="image/webp" :srcset="image.webp">
<source type="image/jpeg" :srcset="image.jpeg">
<img :src="image.fallback" :alt="image.alt">
</picture>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 图片懒加载指令
const vLazy = {
mounted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = binding.value
img.classList.remove('lazy')
observer.unobserve(img)
}
})
})
el.classList.add('lazy')
observer.observe(el)
}
}
// 图片预加载
function preloadImages(urls) {
urls.forEach(url => {
const img = new Image()
img.src = url
})
}
// 图片压缩
function compressImage(file, quality = 0.8) {
return new Promise((resolve) => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const img = new Image()
img.onload = () => {
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)
canvas.toBlob(resolve, 'image/jpeg', quality)
}
img.src = URL.createObjectURL(file)
})
}
</script>
<style scoped>
.lazy-image {
opacity: 0;
transition: opacity 0.3s;
}
.lazy-image:not(.lazy) {
opacity: 1;
}
</style>
内存优化
内存泄漏防护
javascript
// composables/useCleanup.js
import { onUnmounted, ref } from 'vue'
export function useCleanup() {
const cleanupTasks = ref([])
function addCleanupTask(task) {
cleanupTasks.value.push(task)
}
function cleanup() {
cleanupTasks.value.forEach(task => {
try {
task()
} catch (error) {
console.error('Cleanup task failed:', error)
}
})
cleanupTasks.value = []
}
onUnmounted(cleanup)
return {
addCleanupTask,
cleanup
}
}
// 使用示例
export function useWebSocket(url) {
const { addCleanupTask } = useCleanup()
const socket = ref(null)
const connected = ref(false)
function connect() {
socket.value = new WebSocket(url)
socket.value.onopen = () => {
connected.value = true
}
socket.value.onclose = () => {
connected.value = false
}
// 注册清理任务
addCleanupTask(() => {
if (socket.value) {
socket.value.close()
socket.value = null
}
})
}
return {
socket,
connected,
connect
}
}
// 事件监听器清理
export function useEventListener(target, event, handler, options) {
const { addCleanupTask } = useCleanup()
onMounted(() => {
target.addEventListener(event, handler, options)
addCleanupTask(() => {
target.removeEventListener(event, handler, options)
})
})
}
// 定时器清理
export function useInterval(callback, delay) {
const { addCleanupTask } = useCleanup()
const intervalId = ref(null)
function start() {
if (intervalId.value) return
intervalId.value = setInterval(callback, delay)
addCleanupTask(() => {
if (intervalId.value) {
clearInterval(intervalId.value)
intervalId.value = null
}
})
}
function stop() {
if (intervalId.value) {
clearInterval(intervalId.value)
intervalId.value = null
}
}
return { start, stop }
}
大数据处理
javascript
// utils/dataProcessor.js
// 分批处理大数据
export function processBatches(data, batchSize = 1000, processor) {
return new Promise((resolve) => {
const results = []
let index = 0
function processBatch() {
const batch = data.slice(index, index + batchSize)
if (batch.length === 0) {
resolve(results)
return
}
const batchResult = processor(batch)
results.push(...batchResult)
index += batchSize
// 使用 setTimeout 让出主线程
setTimeout(processBatch, 0)
}
processBatch()
})
}
// Web Worker 处理
export function processWithWorker(data, workerScript) {
return new Promise((resolve, reject) => {
const worker = new Worker(workerScript)
worker.postMessage(data)
worker.onmessage = (event) => {
resolve(event.data)
worker.terminate()
}
worker.onerror = (error) => {
reject(error)
worker.terminate()
}
})
}
// 使用示例
export function useDataProcessor() {
const processing = ref(false)
const progress = ref(0)
async function processLargeDataSet(data) {
processing.value = true
progress.value = 0
try {
const result = await processBatches(
data,
1000,
(batch) => {
// 处理批次数据
const processed = batch.map(item => ({
...item,
processed: true,
timestamp: Date.now()
}))
// 更新进度
progress.value = Math.min(100, (progress.value + (1000 / data.length) * 100))
return processed
}
)
return result
} finally {
processing.value = false
progress.value = 100
}
}
return {
processing,
progress,
processLargeDataSet
}
}
构建优化
Vite 配置优化
javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
// 构建优化
build: {
// 代码分割
rollupOptions: {
output: {
manualChunks: {
// 将 Vue 相关库打包到一个 chunk
vue: ['vue', 'vue-router', 'pinia'],
// 将 UI 库打包到一个 chunk
ui: ['element-plus', '@element-plus/icons-vue'],
// 将工具库打包到一个 chunk
utils: ['lodash-es', 'dayjs', 'axios']
}
}
},
// 压缩配置
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// 生成 source map
sourcemap: false,
// 设置 chunk 大小警告限制
chunkSizeWarningLimit: 1000
},
// 依赖预构建
optimizeDeps: {
include: [
'vue',
'vue-router',
'pinia',
'axios',
'lodash-es'
],
exclude: [
// 排除不需要预构建的依赖
]
},
// 服务器配置
server: {
// 预热常用文件
warmup: {
clientFiles: [
'./src/components/**/*.vue',
'./src/views/**/*.vue'
]
}
},
// 别名配置
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@views': resolve(__dirname, 'src/views'),
'@utils': resolve(__dirname, 'src/utils')
}
}
})
代码分割策略
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 按功能模块分割
const routes = [
{
path: '/admin',
component: () => import(/* webpackChunkName: "admin" */ '../layouts/AdminLayout.vue'),
children: [
{
path: 'users',
component: () => import(/* webpackChunkName: "admin-users" */ '../views/admin/Users.vue')
},
{
path: 'settings',
component: () => import(/* webpackChunkName: "admin-settings" */ '../views/admin/Settings.vue')
}
]
},
{
path: '/dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ '../views/Dashboard.vue')
}
]
// 预加载关键路由
const router = createRouter({
history: createWebHistory(),
routes
})
// 路由预加载
router.beforeEach((to, from, next) => {
// 预加载下一个可能访问的路由
if (to.name === 'dashboard') {
import('../views/Profile.vue') // 预加载用户可能访问的页面
}
next()
})
export default router
性能监控
性能指标收集
javascript
// utils/performance.js
// 收集 Core Web Vitals
export function collectWebVitals() {
// Largest Contentful Paint (LCP)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
const lastEntry = entries[entries.length - 1]
console.log('LCP:', lastEntry.startTime)
// 发送到分析服务
sendMetric('LCP', lastEntry.startTime)
}).observe({ entryTypes: ['largest-contentful-paint'] })
// First Input Delay (FID)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
entries.forEach(entry => {
console.log('FID:', entry.processingStart - entry.startTime)
sendMetric('FID', entry.processingStart - entry.startTime)
})
}).observe({ entryTypes: ['first-input'] })
// Cumulative Layout Shift (CLS)
let clsValue = 0
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
entries.forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value
}
})
console.log('CLS:', clsValue)
sendMetric('CLS', clsValue)
}).observe({ entryTypes: ['layout-shift'] })
}
// 发送指标到分析服务
function sendMetric(name, value) {
// 实现发送逻辑
if (navigator.sendBeacon) {
navigator.sendBeacon('/analytics', JSON.stringify({ name, value }))
}
}
// Vue 组件性能监控
export function createPerformancePlugin() {
return {
install(app) {
app.config.globalProperties.$performance = {
mark: (name) => performance.mark(name),
measure: (name, startMark, endMark) => {
performance.measure(name, startMark, endMark)
const measure = performance.getEntriesByName(name)[0]
console.log(`${name}: ${measure.duration}ms`)
return measure.duration
}
}
// 监控组件渲染时间
app.mixin({
beforeCreate() {
this.$performance?.mark(`${this.$options.name}-create-start`)
},
created() {
this.$performance?.mark(`${this.$options.name}-create-end`)
this.$performance?.measure(
`${this.$options.name}-create`,
`${this.$options.name}-create-start`,
`${this.$options.name}-create-end`
)
},
beforeMount() {
this.$performance?.mark(`${this.$options.name}-mount-start`)
},
mounted() {
this.$performance?.mark(`${this.$options.name}-mount-end`)
this.$performance?.measure(
`${this.$options.name}-mount`,
`${this.$options.name}-mount-start`,
`${this.$options.name}-mount-end`
)
}
})
}
}
}
最佳实践总结
开发阶段
组件设计
- 保持组件职责单一
- 合理使用 Props 和 Events
- 避免过深的组件嵌套
状态管理
- 选择合适的响应式 API
- 避免不必要的响应式转换
- 合理使用计算属性和侦听器
模板优化
- 使用 v-memo 优化列表渲染
- 避免在模板中使用复杂表达式
- 合理使用 v-if 和 v-show
构建阶段
代码分割
- 按路由分割代码
- 按功能模块分割
- 提取公共依赖
资源优化
- 压缩代码和资源
- 使用现代图片格式
- 启用 gzip 压缩
缓存策略
- 设置合适的缓存头
- 使用 CDN 加速
- 实现离线缓存
运行时优化
渲染优化
- 使用虚拟滚动处理大列表
- 实现组件懒加载
- 优化事件处理
网络优化
- 实现请求缓存和去重
- 使用数据预加载
- 优化 API 调用
内存管理
- 及时清理事件监听器
- 避免内存泄漏
- 合理使用 KeepAlive
下一步
性能优化是一个持续的过程,需要在开发的各个阶段都保持关注!