Vue 项目优化实践
Vue 项目中常用的性能优化方法和代码示例
问题
在实际工作中,你对 Vue 做过哪些优化?
解答
1. 代码层面优化
v-if 与 v-show 的选择
<template>
<!-- 频繁切换用 v-show,只是 CSS 切换 -->
<div v-show="isVisible">频繁切换的内容</div>
<!-- 条件很少改变用 v-if,真正的条件渲染 -->
<HeavyComponent v-if="isLoaded" />
</template>
v-for 使用唯一 key
<template>
<!-- 使用唯一标识作为 key,避免使用 index -->
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</template>
使用 computed 缓存计算结果
<script setup>
import { ref, computed } from 'vue'
const list = ref([1, 2, 3, 4, 5])
// computed 会缓存结果,依赖不变时不会重新计算
const filteredList = computed(() => {
return list.value.filter(item => item > 2)
})
</script>
使用 keep-alive 缓存组件
<template>
<!-- 缓存组件状态,避免重复渲染 -->
<keep-alive :include="['Home', 'User']" :max="10">
<router-view />
</keep-alive>
</template>
2. 路由懒加载
// router/index.js
const routes = [
{
path: '/home',
// 动态导入,按需加载
component: () => import('@/views/Home.vue')
},
{
path: '/about',
// 使用 webpackChunkName 指定打包名称
component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
}
]
3. 组件异步加载
<script setup>
import { defineAsyncComponent } from 'vue'
// 异步加载重型组件
const HeavyChart = defineAsyncComponent({
loader: () => import('./HeavyChart.vue'),
loadingComponent: LoadingSpinner,
delay: 200,
timeout: 3000
})
</script>
4. 虚拟滚动处理长列表
<template>
<!-- 使用 vue-virtual-scroller 处理大数据列表 -->
<RecycleScroller
class="scroller"
:items="largeList"
:item-size="50"
key-field="id"
>
<template #default="{ item }">
<div class="item">{{ item.name }}</div>
</template>
</RecycleScroller>
</template>
<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
// 假设有 10000 条数据
const largeList = ref([...])
</script>
5. 图片懒加载
<template>
<!-- 使用 loading="lazy" 原生懒加载 -->
<img
v-for="img in images"
:key="img.id"
:src="img.url"
loading="lazy"
alt=""
/>
</template>
<!-- 或使用 v-lazy 指令 -->
<script setup>
// 自定义懒加载指令
const vLazy = {
mounted(el, binding) {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
el.src = binding.value
observer.unobserve(el)
}
})
observer.observe(el)
}
}
</script>
<template>
<img v-lazy="imageUrl" alt="" />
</template>
6. 防抖与节流
<script setup>
import { ref } from 'vue'
import { useDebounceFn, useThrottleFn } from '@vueuse/core'
const searchText = ref('')
// 搜索防抖,停止输入 300ms 后执行
const handleSearch = useDebounceFn((value) => {
console.log('搜索:', value)
}, 300)
// 滚动节流,每 200ms 最多执行一次
const handleScroll = useThrottleFn(() => {
console.log('滚动位置:', window.scrollY)
}, 200)
</script>
<template>
<input v-model="searchText" @input="handleSearch(searchText)" />
</template>
7. 打包优化配置
// 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'],
'element': ['element-plus'],
'echarts': ['echarts']
}
}
},
// 启用 gzip 压缩
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // 移除 console
drop_debugger: true // 移除 debugger
}
}
}
})
8. 使用 shallowRef/shallowReactive
<script setup>
import { shallowRef, shallowReactive } from 'vue'
// 大型对象只需要浅层响应式
const bigData = shallowRef({
// 大量嵌套数据...
})
// 只有第一层属性是响应式的
const state = shallowReactive({
list: [], // list 本身响应式,但 list 内部元素不是
config: {}
})
</script>
关键点
- v-if/v-show:频繁切换用 v-show,条件稳定用 v-if
- 路由懒加载:使用动态 import 按需加载页面组件
- 虚拟滚动:长列表使用虚拟滚动,只渲染可视区域
- 合理使用响应式:大型静态数据用 shallowRef 或 Object.freeze
- 打包分割:第三方库单独打包,利用浏览器缓存
目录