我们的Vue 3项目上线之后性能很差,首屏加载要好几秒,页面切换也卡顿。经过一系列优化之后,性能提升了很多。本文是我们的Vue 3生态性能优化实战总结,包括构建优化、运行时优化、组件优化、路由优化、状态管理优化等。如果你也在用Vue 3,遇到了性能问题,希望这篇文章能帮到你。

一、项目背景

先说说我们的项目情况。

我们的项目是一个中后台管理系统,用Vue 3 + Vite + Vue Router + Pinia + Element Plus开发。项目有几十个页面,上百个组件,代码量比较大。

上线之后发现性能问题很严重:

  • 首屏加载时间要5到8秒,用户体验很差
  • 页面切换的时候有明显的卡顿
  • 某些复杂页面(比如数据表格)渲染很慢
  • 打包之后的JS文件很大,有好几MB

老板要求优化性能,目标是首屏加载控制在3秒以内,页面切换流畅不卡顿。

我负责这次性能优化,花了两周时间,从构建到运行时做了全面的优化。最终首屏加载降到了1.5秒左右,页面切换也很流畅。下面就来分享一下优化的过程和经验。

二、第一步:分析性能瓶颈

优化之前,首先要找到性能瓶颈在哪里。不要上来就优化,要先分析。

我们用了几个工具来分析:

  1. Lighthouse:Chrome的性能分析工具,可以给出性能评分和优化建议
  2. Chrome DevTools的Performance面板:记录页面加载和交互的性能数据,找到耗时的操作
  3. Vite的打包分析:用rollup-plugin-visualizer分析打包后的文件大小,找到大的依赖
  4. Vue DevTools:分析组件的渲染情况,找到不必要的重新渲染

分析之后,我们发现了几个主要的性能瓶颈:

  • 打包体积太大,主要是因为第三方依赖没有按需加载
  • 首屏加载了所有页面的代码,没有做路由懒加载
  • 某些组件没有做优化,导致不必要的重新渲染
  • 图片没有优化,加载了很多大图
  • 没有利用浏览器缓存,每次都要重新加载所有资源

找到了瓶颈之后,就开始针对性地优化。

三、构建优化

构建优化是性能优化的第一步,主要目标是减小打包体积,提升加载速度。

1. 路由懒加载

这是最有效也是最简单的优化。把路由组件改成动态导入,这样首屏只加载当前页面的代码,其他页面的代码在访问的时候才加载。

// 优化前
import UserList from '../views/UserList.vue'

// 优化后
const UserList = () => import('../views/UserList.vue')

改完之后,首屏的JS体积减少了60%,效果非常明显。

2. 第三方依赖按需加载

我们用了Element Plus,最开始是全量引入的,导致打包体积很大。改成按需引入之后,只引入用到的组件,体积减少了很多。

// 优化前
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)

// 优化后:用unplugin-vue-components自动按需引入

除了Element Plus,其他第三方库也要检查,比如lodash、moment这些大库,尽量用按需引入或者替换成更小的库。比如moment可以换成day.js,体积小很多,功能差不多。

3. 图片优化

图片是很多项目容易忽略的优化点。我们的项目里有很多PNG和JPG图片,体积都比较大。

优化方法:

  • 用WebP格式代替PNG和JPG,体积能减少30%到50%
  • 用Vite的imagemin插件自动压缩图片
  • 小图标用SVG或者字体图标,不要用图片
  • 大图用懒加载,不要首屏就加载

改完之后,图片的总体积减少了一半以上。

4. 代码分割

Vite默认会做代码分割,但是可以进一步优化。把第三方依赖单独打包成一个chunk,业务代码单独打包,这样第三方依赖的chunk可以长期缓存,不会因为业务代码变更而失效。

// vite.config.js
build: {
  rollupOptions: {
    output: {
      manualChunks: {
        vue: ['vue', 'vue-router', 'pinia'],
        element: ['element-plus'],
      }
    }
  }
}

5. 开启gzip压缩

在服务器端开启gzip或者brotli压缩,可以大幅减小传输体积。我们用的是Nginx,开启gzip之后,JS和CSS的传输体积减少了60%以上。

四、运行时优化

构建优化解决了加载速度的问题,运行时优化解决的是页面流畅度的问题。

1. 组件懒加载和异步组件

除了路由懒加载,一些不常用的大组件也可以用异步组件,需要的时候才加载。比如弹窗、抽屉、复杂的编辑器等。

<script setup>
import { defineAsyncComponent } from 'vue'
const BigEditor = defineAsyncComponent(() => import('./BigEditor.vue'))
</script>

2. 避免不必要的重新渲染

Vue 3的响应式系统已经很高效了,但是如果使用不当,还是会有很多不必要的重新渲染。

常见的优化方法:

  • 用v-show代替v-if(频繁切换的场景)
  • 用key来优化列表渲染,key要用唯一的ID,不要用索引
  • 用computed缓存计算结果,避免重复计算
  • 大列表用虚拟滚动,不要一次性渲染所有数据
  • 用shallowRef和shallowReactive来减少大对象的响应式开销

我们有一个数据表格页面,一次渲染几百行数据,很卡。后来用了虚拟滚动(vue-virtual-scroller),只渲染可视区域的行,性能提升了很多。

3. 合理使用响应式API

Vue 3的ref和reactive很方便,但是不要滥用。

  • 不需要响应式的数据就不要用ref,用普通变量就行
  • 大对象用shallowReactive,只需要顶层响应式
  • 大量数据用markRaw标记,不做响应式转换

我们有一个场景是从接口拿到大量数据,存到ref里,导致响应式转换很慢。后来用了shallowRef,只在需要的时候手动触发更新,性能提升了很多。

4. 防抖和节流

对于频繁触发的事件,比如输入、滚动、resize,要用防抖或者节流。

import { debounce } from 'lodash-es'

const handleSearch = debounce((value) => {
  // 搜索逻辑
}, 300)

我们的搜索框最开始是每次输入都发请求,导致请求很多,页面也卡。加了防抖之后,体验好了很多。

五、路由优化

1. 路由懒加载

前面已经说了,路由懒加载是必须的。

2. 路由预加载

对于一些用户很可能访问的页面,可以在首屏加载完成之后,用浏览器的空闲时间预加载。这样用户点击的时候,代码已经加载好了,切换很快。

Vite可以用magic comment来指定预加载:

const UserList = () => import(/* webpackPrefetch: true */ '../views/UserList.vue')

3. 路由切换动画优化

路由切换的时候,如果有动画,要注意动画的性能。用CSS transform和opacity来做动画,不要用top、left这些会触发重排的属性。

六、状态管理优化

我们用的是Pinia,Vue 3官方推荐的状态管理库。

1. 合理设计store

不要把所有数据都放到一个store里,按照业务模块拆分store。每个store只存相关的数据,避免不必要的全局响应式。

2. 避免在store里存大量数据

store里的数据都是响应式的,如果存大量数据,会有性能开销。大量数据最好存在组件里,或者用shallowRef。

3. 用computed派生状态

需要从state派生的数据,用getter(computed),不要在组件里重复计算。Pinia的getter是有缓存的,只有依赖变化的时候才会重新计算。

七、网络优化

1. HTTP缓存

静态资源(JS、CSS、图片)设置长期缓存,文件名带hash,内容变化的时候hash变化,缓存自动失效。这样用户第二次访问的时候,静态资源直接从缓存读,不需要重新下载。

2. 接口缓存

对于不常变化的接口数据,可以做前端缓存。比如字典数据、配置数据,请求一次之后缓存起来,下次直接用缓存。

3. 接口合并和预请求

减少接口请求的数量,能合并的接口就合并。首屏需要的数据,可以在页面加载之前就预请求,不要等组件渲染的时候才请求。

八、优化效果

经过两周的优化,我们的项目性能提升很明显:

  • 首屏加载时间:从5-8秒降到了1.5秒左右
  • 打包体积:从3MB降到了800KB(gzip之后250KB)
  • 页面切换:基本无卡顿,很流畅
  • 复杂表格页面:渲染时间从几秒降到了几百毫秒
  • Lighthouse性能评分:从30多分提升到了90分以上

老板对优化结果很满意,用户体验也提升了很多。

九、经验总结

这次性能优化,我总结了一些经验。

1. 先分析再优化

不要上来就优化,先找到瓶颈在哪里。用工具分析,用数据说话,针对性地优化,效率最高。

2. 构建优化性价比最高

路由懒加载、按需引入、图片压缩这些构建优化,改起来简单,效果明显,是性价比最高的优化,应该优先做。

3. 运行时优化要谨慎

运行时优化(比如shallowRef、虚拟滚动)改起来比较复杂,而且可能引入bug。要在确实有性能问题的地方做,不要过度优化。

4. 性能优化是持续的过程

性能优化不是一次性的工作,项目在不断迭代,新的代码可能会引入新的性能问题。要建立性能监控机制,定期检查,持续优化。

5. 关注用户体验

性能优化的最终目标是提升用户体验,不是为了追求数字。有些优化虽然指标好看,但是用户体验没提升,就没有意义。要从用户的角度出发,关注真正影响体验的地方。

十、写在最后

Vue 3的性能已经很好了,但是如果项目大了、代码写得不规范,还是会有性能问题。性能优化是一个系统工程,需要从构建、运行时、网络等多个方面入手。

希望我们的优化经验能帮到正在做Vue 3性能优化的你。记住,先分析再优化,用数据说话,针对性地解决问题。

最后用一句话结束本文:"性能优化没有银弹,只有持续的关注和改进。"愿每一个前端开发者都能做出又快又好的应用。