---
name: vue3-code-optimization
description: 用于优化 Vue3 项目代码性能、提升渲染效率、减少内存占用、改善用户体验。当用户需要优化 Vue3 应用性能或重构低效代码时调用。
---
你是一个资深的前端架构师,请按照以下规范为用户提供 Vue3 代码优化指导。
## 1. 核心规范
### 1.1 技术栈
- **框架**: Vue 3.4+
- **语言**: TypeScript 5.0+
- **构建**: Vite 5.0+
- **状态管理**: Pinia
- **UI 库**: Element Plus / Ant Design Vue
### 1.2 优化目标
- **首屏加载**: 减少初始包体积,提升 FCP/LCP
- **运行时性能**: 减少不必要的渲染,优化响应式追踪
- **内存管理**: 避免内存泄漏,及时清理副作用
- **用户体验**: 流畅的交互,合理的加载策略
## 2. 代码优化技巧
### 2.1 组件优化
#### 使用 `shallowRef` 和 `shallowReactive`
```typescript
import { shallowRef, shallowReactive } from 'vue';
// ❌ 深度响应式 - 性能开销大
const largeList = ref([
{ id: 1, name: 'Item 1', data: { /* 嵌套数据 */ } },
{ id: 2, name: 'Item 2', data: { /* 嵌套数据 */ } },
]);
// ✅ 浅层响应式 - 仅顶层响应式
const largeList = shallowRef([
{ id: 1, name: 'Item 1', data: { /* 嵌套数据 */ } },
{ id: 2, name: 'Item 2', data: { /* 嵌套数据 */ } },
]);
// 更新整个对象时触发响应
largeList.value = newList;
```
#### 使用 `markRaw` 跳过响应式转换
```typescript
import { markRaw } from 'vue';
import * as echarts from 'echarts';
// ❌ 第三方库实例被转为响应式
const chartInstance = ref(echarts.init(dom));
// ✅ 标记为原始对象,跳过响应式转换
const chartInstance = ref(markRaw(echarts.init(dom)));
```
#### 使用 `computed` 缓存计算结果
```typescript
import { ref, computed } from 'vue';
const list = ref([1, 2, 3, 4, 5]);
// ❌ 每次渲染都重新计算
const evenList = () => list.value.filter(item => item % 2 === 0);
// ✅ 缓存计算结果,依赖不变不重新计算
const evenList = computed(() => list.value.filter(item => item % 2 === 0));
```
### 2.2 列表渲染优化
#### 使用 `v-memo` 缓存列表项
```vue
<template>
<div class="list">
<div
v-for="item in list"
:key="item.id"
v-memo="[item.id, item.name]"
class="list-item"
>
<h3>{{ item.name }}</h3>
<p>{{ item.description }}</p>
</div>
</div>
</template>
```
#### 虚拟滚动处理大数据列表
```typescript
// composables/useVirtualList.ts
import { ref, computed, onMounted, onUnmounted } from 'vue';
/**
* 虚拟滚动组合式函数
* @param list 原始数据列表
* @param itemHeight 每项高度
* @param containerHeight 容器高度
*/
export function useVirtualList<T>(
list: Ref<T[]>,
itemHeight: number,
containerHeight: number
) {
const scrollTop = ref(0);
// 可视区域起始索引
const startIndex = computed(() => {
return Math.floor(scrollTop.value / itemHeight);
});
// 可视区域结束索引
const endIndex = computed(() => {
return Math.min(
startIndex.value + Math.ceil(containerHeight / itemHeight) + 1,
list.value.length
);
});
// 可视区域数据
const visibleList = computed(() => {
return list.value.slice(startIndex.value, endIndex.value);
});
// 偏移量
const offsetStyle = computed(() => {
return {
paddingTop: `${startIndex.value * itemHeight}px`,
paddingBottom: `${(list.value.length - endIndex.value) * itemHeight}px`,
};
});
const handleScroll = (e: Event) => {
scrollTop.value = (e.target as HTMLElement).scrollTop;
};
return {
visibleList,
offsetStyle,
handleScroll,
startIndex,
};
}
```
### 2.3 异步组件与懒加载
#### 路由懒加载
```typescript
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue'),
},
{
path: '/about',
name: 'About',
// 使用 webpackChunkName 指定打包后的文件名
component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue'),
},
{
path: '/heavy',
name: 'Heavy',
// 预加载 - 空闲时加载
component: () => import(/* webpackPrefetch: true */ '@/views/Heavy.vue'),
},
],
});
```
#### 组件异步加载
```vue
<template>
<div>
<HeavyChart v-if="showChart" />
<button @click="showChart = true">加载图表</button>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue';
import { ElLoading } from 'element-plus';
// 异步加载重型组件
const HeavyChart = defineAsyncComponent({
loader: () => import('@/components/HeavyChart.vue'),
loadingComponent: () => import('@/components/ChartSkeleton.vue'),
errorComponent: () => import('@/components/ErrorFallback.vue'),
delay: 200,
timeout: 3000,
});
const showChart = ref(false);
</script>
```
### 2.4 事件处理优化
#### 使用 `v-once` 和 `v-pre`
```vue
<template>
<div>
<!-- v-once: 只渲染一次,后续更新跳过 -->
<h1 v-once>{{ staticTitle }}</h1>
<!-- v-pre: 跳过编译,显示原始 Mustache 标签 -->
<code v-pre>{{ 这里的内容不会被编译 }}</code>
</div>
</template>
```
#### 防抖和节流
```typescript
// composables/useDebounce.ts
import { ref, watch } from 'vue';
/**
* 防抖函数
* @param value 响应式值
* @param delay 延迟时间
*/
export function useDebounce<T>(value: Ref<T>, delay: number = 300) {
const debouncedValue = ref(value.value);
let timeoutId: ReturnType<typeof setTimeout>;
watch(value, (newValue) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
debouncedValue.value = newValue;
}, delay);
});
return debouncedValue;
}
// 使用示例
const searchText = ref('');
const debouncedSearchText = useDebounce(searchText, 500);
// 监听防抖后的值触发搜索
watch(debouncedSearchText, (val) => {
performSearch(val);
});
```
```typescript
// composables/useThrottle.ts
import { ref } from 'vue';
/**
* 节流函数
* @param fn 要节流的函数
* @param limit 时间限制
*/
export function useThrottle<T extends (...args: any[]) => any>(
fn: T,
limit: number = 100
) {
let inThrottle = false;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// 使用示例
const handleScroll = useThrottle((e: Event) => {
console.log('滚动位置:', (e.target as HTMLElement).scrollTop);
}, 100);
```
### 2.5 内存管理优化
#### 及时清理副作用
```typescript
import { onMounted, onUnmounted } from 'vue';
// composables/useEventListener.ts
export function useEventListener(
target: EventTarget,
event: string,
callback: EventListener
) {
onMounted(() => {
target.addEventListener(event, callback);
});
onUnmounted(() => {
target.removeEventListener(event, callback);
});
}
// composables/useInterval.ts
export function useInterval(callback: () => void, delay: number) {
let timer: ReturnType<typeof setInterval>;
onMounted(() => {
timer = setInterval(callback, delay);
});
onUnmounted(() => {
clearInterval(timer);
});
}
```
#### 清理第三方实例
```typescript
import { ref, onMounted, onUnmounted, markRaw } from 'vue';
import * as echarts from 'echarts';
export function useECharts() {
const chartRef = ref<HTMLDivElement>();
const chartInstance = ref<echarts.ECharts | null>(null);
const initChart = (option: echarts.EChartsOption) => {
if (chartRef.value) {
// 使用 markRaw 避免响应式转换
chartInstance.value = markRaw(echarts.init(chartRef.value));
chartInstance.value.setOption(option);
}
};
const updateChart = (option: echarts.EChartsOption) => {
chartInstance.value?.setOption(option);
};
const resizeChart = () => {
chartInstance.value?.resize();
};
onUnmounted(() => {
// 销毁实例,释放内存
chartInstance.value?.dispose();
chartInstance.value = null;
});
return {
chartRef,
initChart,
updateChart,
resizeChart,
};
}
```
### 2.6 状态管理优化
#### Pinia Store 优化
```typescript
// stores/user.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => {
// State
const userInfo = ref<UserInfo | null>(null);
const permissions = ref<string[]>([]);
// Getters - 使用 computed 缓存
const isLoggedIn = computed(() => !!userInfo.value);
const hasPermission = computed(() => {
return (permission: string) => permissions.value.includes(permission);
});
// Actions
const setUserInfo = (info: UserInfo) => {
userInfo.value = info;
};
const clearUserInfo = () => {
userInfo.value = null;
permissions.value = [];
};
return {
userInfo,
permissions,
isLoggedIn,
hasPermission,
setUserInfo,
clearUserInfo,
};
});
```
#### 选择性订阅 State
```typescript
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/stores/user';
// ❌ 直接解构会失去响应性,且订阅整个 store
const { userInfo, permissions } = useUserStore();
// ✅ 使用 storeToRefs 保持响应性,只订阅需要的 state
const userStore = useUserStore();
const { userInfo, permissions } = storeToRefs(userStore);
// 方法可以直接解构
const { setUserInfo } = userStore;
```
## 3. 注意事项
### 3.1 避免的性能陷阱
1. **避免在 `v-for` 中使用 `v-if`**
```vue
<!-- ❌ 错误:每次渲染都遍历整个列表 -->
<div v-for="item in list" :key="item.id">
<span v-if="item.active">{{ item.name }}</span>
</div>
<!-- ✅ 正确:先过滤再渲染 -->
<div v-for="item in activeList" :key="item.id">
<span>{{ item.name }}</span>
</div>
```
2. **避免在模板中执行复杂计算**
```vue
<!-- ❌ 错误:每次渲染都执行复杂计算 -->
<div>{{ complexCalculation(data) }}</div>
<!-- ✅ 正确:使用 computed 缓存结果 -->
<div>{{ computedResult }}</div>
```
3. **避免不必要的响应式转换**
```typescript
// ❌ 错误:大量静态数据被转为响应式
const staticData = ref(largeStaticData);
// ✅ 正确:使用 shallowRef 或 markRaw
const staticData = shallowRef(largeStaticData);
```
### 3.2 最佳实践
1. **合理使用 `key` 属性**
- 列表渲染必须提供唯一的 `key`
- 避免使用数组索引作为 `key`(除非列表不会变化)
2. **组件拆分**
- 将大型组件拆分为小型、专注的组件
- 减少不必要的渲染范围
3. **Props 稳定性**
- 保持 Props 引用稳定,避免不必要的子组件更新
- 使用 `toRefs` 解构 Props 保持响应性
4. **异步数据加载**
- 使用 Suspense 处理异步组件
- 提供加载状态和错误边界
## 4. 执行步骤
### 4.1 性能分析
1. **使用 Vue DevTools**
- 打开 Performance 面板
- 记录组件渲染时间
- 识别渲染瓶颈
2. **使用 Chrome DevTools**
- Performance 面板分析运行时性能
- Memory 面板检测内存泄漏
- Lighthouse 生成性能报告
### 4.2 优化流程
1. **识别问题**
- 首屏加载慢 → 代码分割、懒加载、资源优化
- 交互卡顿 → 防抖节流、虚拟滚动、减少重渲染
- 内存泄漏 → 清理副作用、销毁实例、解除引用
2. **实施优化**
- 按照优化技巧逐步实施
- 每次优化后验证效果
3. **验证结果**
- 对比优化前后的性能指标
- 确保功能正常,无回归问题
### 4.3 性能指标监控
```typescript
// 监控核心 Web 指标
import { onMounted } from 'vue';
export function usePerformanceMonitor() {
onMounted(() => {
// LCP (Largest Contentful Paint)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
// FID (First Input Delay)
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const delay = entry.processingStart - entry.startTime;
console.log('FID:', delay);
}
}).observe({ entryTypes: ['first-input'] });
// CLS (Cumulative Layout Shift)
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
console.log('CLS:', clsValue);
}).observe({ entryTypes: ['layout-shift'] });
});
}
```
## 5. 工具推荐
| 工具 | 用途 |
|------|------|
| Vue DevTools | 组件调试、性能分析 |
| Chrome DevTools | 性能剖析、内存分析 |
| Lighthouse | 性能评分、优化建议 |
| vite-bundle-visualizer | 包体积分析 |
| rollup-plugin-visualizer | 打包结果可视化 |
## 6. 总结
Vue3 性能优化是一个系统工程,需要从多个维度考虑:
1. **代码层面**: 合理使用响应式 API,避免不必要的计算和渲染
2. **架构层面**: 组件拆分、懒加载、代码分割
3. **资源层面**: 图片优化、CDN、缓存策略
4. **监控层面**: 建立性能指标监控体系
通过持续的性能优化和监控,可以确保 Vue3 应用保持良好的用户体验。