标题提到的Vue 3.3在本文写作时(2022年7月)尚未正式发布(实际于2023年5月发布)。本文基于Vue 3.2的性能优化实践,结合Vue 3.3的新特性预期,分享一次真实的性能优化经历。
上个月,我接手了一个Vue项目,用户反馈页面加载慢、滚动卡顿、操作不流畅。我花了一周时间,对项目进行了全面的性能优化,把首屏加载时间从5秒降到了1.5秒,滚动也流畅了很多。
本文分享这次性能优化的完整过程,包括问题分析、优化思路、具体做法、优化效果,以及一些通用的性能优化经验。
一、问题分析
先说说项目存在的问题。
1. 首屏加载慢
用户打开页面,要等5秒以上才能看到内容。白屏时间很长,用户体验很差。
我用Chrome DevTools分析了一下,发现:
- 打包后的JS文件有3MB,太大了
- 没有做代码分割,所有代码都在一个文件里
- 没有懒加载,所有页面的代码都在首屏加载
- 图片没有压缩,有些图片几MB大
2. 滚动卡顿
页面滚动的时候,明显卡顿,尤其是列表页。
分析发现:
- 列表没有做虚拟滚动,一次渲染几百条数据
- 图片没有懒加载,滚动时同时加载大量图片
- 滚动事件没有防抖,频繁触发重渲染
- 组件没有做优化,每次滚动都重新渲染
3. 操作不流畅
用户点击按钮、切换标签,有明显的延迟。
分析发现:
- 状态管理不合理,一个状态变化导致整个页面重渲染
- 没有用computed缓存计算结果
- 大列表没有用key优化
- 第三方库太重,占用了主线程
二、优化思路
面对这些问题,我制定了优化思路:
- 先测量,再优化:用工具找到瓶颈,不要盲目优化
- 从大到小:先解决影响最大的问题(打包体积、首屏加载),再解决细节问题
- 逐步验证:每优化一项,就测试一下效果,确保优化有效
- 不破坏功能:优化不能影响功能,改完要充分测试
三、打包优化
打包优化是收益最大的部分。
1. 分析打包体积
首先用webpack-bundle-analyzer分析打包体积。
// vue.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
configureWebpack: {
plugins: [new BundleAnalyzerPlugin()]
}
}分析后发现,体积最大的几个部分:
- element-plus:800KB
- echarts:600KB
- lodash:400KB
- moment.js:300KB
- 业务代码:500KB
- 其他:400KB
2. 按需引入第三方库
很多第三方库支持按需引入,不需要全部引入。
element-plus按需引入:
// 之前
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)
// 之后:用unplugin-vue-components自动按需引入
// vue.config.js
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = {
configureWebpack: {
plugins: [
AutoImport({ resolvers: [ElementPlusResolver()] }),
Components({ resolvers: [ElementPlusResolver()] })
]
}
}按需引入后,element-plus从800KB降到了200KB。
echarts按需引入:
// 之前
import * as echarts from 'echarts'
// 之后
import * as echarts from 'echarts/core'
import { BarChart, LineChart } from 'echarts/charts'
import { GridComponent, TooltipComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([BarChart, LineChart, GridComponent, TooltipComponent, CanvasRenderer])按需引入后,echarts从600KB降到了200KB。
lodash按需引入:
// 之前
import _ from 'lodash'
// 之后
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'或者用babel-plugin-lodash自动转换。
3. 替换轻量级库
有些库可以用更轻量的替代。
- moment.js → dayjs(从300KB降到20KB)
- axios → 原生fetch(如果需求简单)
- 完整的富文本编辑器 → 轻量级编辑器
替换moment.js为dayjs,省了280KB。
4. 代码分割
用路由懒加载,把不同页面的代码分割开。
// 之前
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
// 之后
const routes = [
{ path: '/', component: () => import('@/views/Home.vue') },
{ path: '/about', component: () => import('@/views/About.vue') }
]这样,首屏只加载当前页面的代码,其他页面的代码在需要时才加载。
5. 优化效果
打包优化后:
- 首屏JS体积:从3MB降到了800KB
- gzip后:从1MB降到了250KB
- 首屏加载时间:从5秒降到了2秒
四、渲染优化
打包优化解决了加载慢的问题,接下来解决渲染卡顿的问题。
1. 虚拟列表
大列表是渲染卡顿的主要原因。我们的列表页一次渲染几百条数据,每条数据都是一个复杂的组件,导致DOM节点太多,渲染慢。
用虚拟列表(vue-virtual-scroller)优化:
<template>
<RecycleScroller
class="scroller"
:items="items"
:item-size="80"
key-field="id"
>
<template #default="{ item }">
<ListItem :item="item" />
</template>
</RecycleScroller>
</template>虚拟列表只渲染可视区域的元素,不管列表有多少条数据,DOM节点只有几十个。
优化后,列表滚动从卡顿变成了丝滑。
2. 图片懒加载
图片懒加载,只加载可视区域的图片。
用vue-lazyload或者原生的loading="lazy":
<!-- 原生懒加载 -->
<img src="image.jpg" loading="lazy" />
<!-- 或者用vue-lazyload -->
<img v-lazy="image.jpg" />图片懒加载后,滚动时不会同时加载大量图片,减少了网络请求和渲染压力。
3. 组件优化
用Vue的性能优化API优化组件。
shallowRef和shallowReactive: 对于大对象,如果不需要深层响应式,用shallowRef或shallowReactive:
import { shallowRef } from 'vue'
// 大列表,不需要深层响应式
const list = shallowRef([])computed缓存: 计算属性用computed缓存,不要在模板里写复杂的计算:
<template>
<!-- 不推荐:每次渲染都重新计算 -->
<div>{{ items.filter(i => i.active).map(i => i.name).join(',') }}</div>
<!-- 推荐:用computed缓存 -->
<div>{{ activeNames }}</div>
</template>
<script setup>
import { computed } from 'vue'
const activeNames = computed(() => {
return items.value.filter(i => i.active).map(i => i.name).join(',')
})
</script>v-memo: v-memo可以缓存组件的渲染结果,依赖不变时跳过渲染:
<div v-memo="[item.id, item.name]">
<!-- 只有item.id或item.name变化时才重新渲染 -->
<ComplexComponent :item="item" />
</div>4. 合理使用key
v-for一定要加key,而且用稳定的唯一值(如id),不要用index:
<!-- 不推荐 -->
<div v-for="(item, index) in list" :key="index">
<!-- 推荐 -->
<div v-for="item in list" :key="item.id">用index作为key,在列表排序或插入删除时,会导致不必要的重渲染。
5. 防抖和节流
滚动、输入等频繁触发的事件,用防抖和节流优化:
import { debounce, throttle } from 'lodash'
// 输入搜索:防抖
const handleSearch = debounce((keyword) => {
search(keyword)
}, 300)
// 滚动加载:节流
const handleScroll = throttle(() => {
checkLoadMore()
}, 100)五、网络优化
除了打包和渲染,网络优化也很重要。
1. 图片优化
- 压缩图片:用tinypng或webpack的image-minimizer插件
- 用WebP格式:比JPG/PNG小30%左右
- 用CDN:静态资源放CDN,加快加载速度
- 响应式图片:根据屏幕大小加载不同尺寸的图片
2. 接口优化
- 接口合并:减少请求次数,把多个小接口合并成一个
- 接口缓存:不常变化的数据,做缓存
- 预加载:提前加载用户可能需要的数据
- 骨架屏:加载时显示骨架屏,提升感知体验
3. HTTP缓存
合理设置HTTP缓存头:
- 静态资源(JS、CSS、图片):长期缓存(Cache-Control: max-age=31536000)
- HTML:协商缓存(ETag、Last-Modified)
- 接口数据:根据业务需求设置缓存时间
六、内存优化
内存优化容易被忽略,但对长时间运行的应用很重要。
1. 及时清理定时器和事件监听
组件卸载时,清理定时器和事件监听:
import { onMounted, onUnmounted } from 'vue'
let timer = null
let handleResize = null
onMounted(() => {
timer = setInterval(() => {
// 定时任务
}, 1000)
handleResize = () => {
// 处理窗口大小变化
}
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
clearInterval(timer)
window.removeEventListener('resize', handleResize)
})2. 避免内存泄漏
- 全局事件监听要及时移除
- 闭包引用的大对象要及时释放
- 长列表不要无限追加,要做分页或虚拟列表
- 第三方库的实例要及时销毁(如echarts实例)
3. 大对象及时释放
不再使用的大对象,设置为null,让GC回收:
let bigData = null
const loadData = async () => {
bigData = await fetchBigData()
// 使用bigData
}
const clearData = () => {
bigData = null
}七、Vue 3.3的新特性预期
虽然Vue 3.3还没发布,但根据目前的信息,有一些性能相关的新特性值得期待:
- 更好的TypeScript类型推导,减少运行时开销
- 编译优化,更小的打包体积
- 更快的虚拟DOM diff算法
- 改进的响应式系统,减少不必要的更新
等Vue 3.3发布后,可以进一步利用这些新特性优化性能。
八、优化效果
经过一周的优化,效果很明显:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 首屏加载时间 | 5秒 | 1.5秒 | 70% |
| 首屏JS体积 | 3MB | 800KB | 73% |
| gzip后体积 | 1MB | 250KB | 75% |
| 列表滚动FPS | 30 | 60 | 100% |
| 内存占用 | 500MB | 200MB | 60% |
用户反馈页面快了很多,操作也流畅了。
九、性能优化的经验总结
这次优化,我总结了一些经验。
1. 先测量,再优化
不要凭感觉优化,要用工具找到真正的瓶颈。
- 打包体积:webpack-bundle-analyzer
- 加载性能:Chrome DevTools的Network和Performance
- 渲染性能:Chrome DevTools的Performance和Rendering
- 内存:Chrome DevTools的Memory
2. 二八定律
80%的性能问题,来自20%的代码。找到那20%,重点优化。
比如,打包体积最大的几个第三方库,优化它们收益最大;渲染最慢的几个组件,优化它们效果最明显。
3. 不要过度优化
优化是有成本的,不要为了优化而优化。
- 不要为了减少几KB,把代码写得很难维护
- 不要为了性能,牺牲用户体验
- 不要提前优化,先让功能跑起来,再优化瓶颈
4. 持续监控
性能优化不是一次性的,要持续监控。
- 用性能监控工具(如Sentry、Web Vitals)持续监控
- 每次代码更新,检查性能是否退化
- 建立性能预算,超过预算就报警
5. 用户感知比数据更重要
有时候,数据上的优化,用户不一定能感知到。而用户感知到的快,才是真的快。
- 骨架屏:虽然加载时间没变,但用户觉得快了
- 懒加载:虽然总加载量没变,但首屏快了
- 乐观更新:虽然请求还没完成,但用户觉得操作成功了
十、写在最后
性能优化,是前端开发中非常重要的一环。
一个性能好的应用,用户用着舒服,也更愿意留下来。一个性能差的应用,用户可能打开一次就再也不来了。
这次Vue项目的性能优化,让我对性能优化有了更深的理解。打包优化、渲染优化、网络优化、内存优化,每个方面都有很多细节需要注意。
2022年了,Vue 3已经成为主流,Vue 3.3也即将发布。新的版本会带来更好的性能,但框架的优化只是基础,真正的性能优化,还是要靠开发者在项目中实践。
最后,用一句话总结:"性能优化没有银弹,只有不断地测量、分析、优化。找到瓶颈,重点突破,持续监控,才能让应用越来越快。"
愿你的Vue应用,又快又稳。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录