第7课:Vue 3应用性能优化与进阶实战——让你的应用更快、更流畅

在前 6 课中,我们已经完成了 Vue 3 应用的 "从开发到上线" 全流程,得到了一个功能完整、可公开访问的待办应用。但在实际开发中,"能运行" 只是基础,"运行得快、体验流畅" 才是企业级应用的核心要求 ------ 比如页面加载慢、列表滚动卡顿、操作响应延迟等问题,都会直接影响用户体验。本节课将聚焦性能优化核心方案和进阶实战,从 "加载阶段""运行阶段""大型数据渲染" 三个关键场景切入,教你用 Vue 3 专属优化手段解决性能瓶颈,让你的应用从 "能用" 升级为 "好用、快用"。

一、课前准备:性能优化前的基础铺垫(10 分钟搞定)

优化前需要先明确 "优化目标" 和 "测试工具",避免盲目优化(先定位问题,再解决问题),新手直接照做即可:

1. 必备工具:性能监控与测试工具

  • Lighthouse(Chrome 内置,核心推荐):用于全面评估应用性能(加载速度、交互流畅度等),生成可视化报告和优化建议,新手优先用这个;
  • Vue DevTools(Vue 官方调试工具):查看组件渲染状态、Pinia 状态变化,定位 "不必要的组件渲染" 问题;
  • Chrome 性能面板(Performance):进阶工具,用于录制页面操作流程,分析卡顿原因(比如长任务阻塞线程)。

2. 课前准备步骤

  1. 确保待办应用可正常运行(本地npm run dev或线上域名);
  2. 安装 Vue DevTools(Chrome 浏览器扩展商店搜索 "Vue DevTools",选择 Vue 3 版本);
  3. 用 Lighthouse 生成基础性能报告:打开 Chrome 浏览器→按 F12 打开开发者工具→切换到 Lighthouse 面板→勾选 "Performance"(性能)→点击 "Generate report"(生成报告),等待 1-2 分钟即可得到初始性能分数(满分 100,低于 80 需要优化)。

💡 核心原则:性能优化不是 "盲目调优",而是 "先测后改"------ 先通过工具定位性能瓶颈(比如是加载慢还是渲染卡),再针对性解决,避免做 "无效优化"。

3. 课前知识铺垫(通俗理解核心概念)

  • 加载性能:指从输入网址到页面完全加载完成的速度,核心影响因素是 "资源体积"(JS/CSS/ 图片)和 "请求数量";
  • 运行时性能:指页面加载完成后,用户操作(点击、滚动、输入)的响应速度,核心影响因素是 "不必要的组件渲染" 和 "长任务阻塞";
  • 虚拟列表:针对 "大型列表渲染"(比如 1000 + 条数据)的优化方案,只渲染 "当前视口可见的列表项",大幅减少 DOM 节点数量,解决滚动卡顿问题。

二、核心实操一:加载性能优化 ------ 让页面 "秒开"

加载慢是前端应用最常见的性能问题,尤其在弱网环境下。Vue 3 项目的加载优化主要围绕 "减少资源体积""减少请求数量""优先加载关键资源" 三个方向,以下是新手可直接落地的 4 个核心方案:

1. 方案 1:路由懒加载(减少初始加载资源体积)

之前的路由配置是 "一次性加载所有页面组件",即使用户没访问的页面(比如待办详情页),也会在初始加载时被打包,导致初始资源体积过大。路由懒加载可实现 "按需加载"------ 只有用户访问某个路由时,才加载对应的组件。

实操:修改src/router/index.js,用 "动态 import" 替换直接导入:

javascript

运行

复制代码
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// 路由懒加载:用import(() => import('组件路径'))替代直接import
const Home = () => import('../views/Home.vue')
const TodoList = () => import('../views/TodoList.vue')
const TodoDetail = () => import('../views/TodoDetail.vue')

const routes = [
  { path: '/', name: 'Home', component: Home },
  { path: '/todo', name: 'TodoList', component: TodoList },
  { path: '/todo/:id', name: 'TodoDetail', component: TodoDetail }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

export default router

效果验证:启动项目后,打开 Chrome 开发者工具→Network 面板→刷新页面,会发现初始加载时只加载了Home.vue对应的 JS 文件,点击 "进入待办列表" 后,才会加载TodoList.vue的 JS 文件,初始加载体积减少 30%-50%。

2. 方案 2:图片优化(减少图片资源体积)

图片是页面资源体积的 "大头",优化图片能快速提升加载速度,Vue 项目中推荐两种新手友好的优化方案:

(1)使用现代图片格式(WebP/AVIF)

WebP 格式比 JPG/PNG 体积小 30%-50%,且画质无损,主流浏览器都支持。实操:将项目中的图片(比如assets/logo.png)转换为 WebP 格式(用在线工具https://convertio.co/zh/png-webp/),然后在组件中使用:

vue

复制代码
<template>
  <div class="home">
<!-- 使用WebP格式<img src="@/assets/logo.webp" alt="Vue Logo" class="logo">
 </template>
(2)图片懒加载(按需加载可视区域图片)

用 Vue 3 的v-lazy指令(需配合vue-lazyload插件)实现 "只有图片进入视口时才加载",避免初始加载所有图片。

  1. 安装插件:npm install vue-lazyload -S
  2. main.js中注册:

javascript

运行

复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import App from './App.vue'
import './style.css'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// 引入图片懒加载插件
import VueLazyload from 'vue-lazyload'

const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
// 注册图片懒加载插件(可配置加载中/错误占位图)
app.use(VueLazyload, {
  loading: '@/assets/loading.gif', // 加载中占位图(可选)
  error: '@/assets/error.png'      // 加载失败占位图(可选)
})
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}
app.mount('#app')
  1. 在组件中使用v-lazy替代src:<template<div class<img v-lazy="require('@/assets/logo.webp')" alt="Vue Logo" class="logo</div></template>

plaintext

复制代码
### 3. 方案3:第三方依赖按需引入(减少打包体积)
之前我们全局引入了Element Plus(`app.use(ElementPlus)`),会将Element Plus的所有组件都打包进项目,即使只用到了按钮、输入框等少数组件。按需引入能只打包"用到的组件",减少体积。

实操:使用Element Plus官方按需引入插件(新手直接复制步骤):
1.  安装按需引入插件:`npm install unplugin-vue-components unplugin-auto-import -D`;
2.  修改`vite.config.js`,添加插件配置:
```javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// 引入Element Plus按需引入插件
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    // 自动导入Element Plus的API(比如ElMessage)
    AutoImport({
      resolvers: [ElementPlusResolver()]
    }),
    // 自动导入Element Plus的组件
    Components({
      resolvers: [ElementPlusResolver()]
    })
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  },
  build: {
    // 之前的打包优化配置不变...
  },
  server: {
    // 之前的代理配置不变...
  }
})
  1. 修改main.js,删除全局引入 Element Plus 的代码:

javascript

运行

复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import App from './App.vue'
import './style.css'
// 删除以下3行全局引入代码
// import ElementPlus from 'element-plus'
// import 'element-plus/dist/index.css'
// import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)
app.use(createPinia())
app.use(router)
// 删除全局注册Element Plus的代码
// app.use(ElementPlus)
// for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
//   app.component(key, component)
// }
app.mount('#app')

效果验证:执行npm run build,对比优化前后的dist文件夹体积,会发现体积减少 20%-40%(取决于用到的组件数量)。组件使用方式不变(比如直接<el-button>`),插件会自动按需导入。

4. 方案 4:Gzip/Brotli 压缩(服务器端优化,快速见效)

Gzip/Brotli 是服务器端的压缩方案,能将 JS/CSS/HTML 等文本资源体积再压缩 50%-70%,Netlify、GitHub Pages 等免费平台默认支持,无需手动配置。

验证:部署优化后的项目到 Netlify,打开 Chrome 开发者工具→Network 面板→刷新页面,查看 JS/CSS 文件的 "Content-Encoding" 字段,若显示 "gzip" 或 "br",说明压缩生效。

三、核心实操二:运行时性能优化 ------ 让操作 "丝滑"

页面加载完成后,用户操作(点击、输入、滚动)的响应速度直接影响体验。运行时优化的核心是 "减少不必要的组件渲染" 和 "避免长任务阻塞",以下是 3 个 Vue 3 专属优化方案:

1. 方案 1:用 computed 缓存计算结果(避免重复计算)

如果组件中有频繁使用的计算逻辑(比如筛选待办列表),直接在模板中写计算逻辑会导致 "每次组件渲染都重复计算",用computed能缓存计算结果,只有依赖数据变化时才重新计算。

实操:在TodoList.vue中添加 "筛选已完成待办" 的功能,用computed缓存结果:<template><div class="todo-list-page">

<el-radio-group v-model="filterType" class="filter-group"><el-radio label</el-radio><el-radio label="completed</el-r<el-radio label="active"></el-radio</el-r<!-- 渲染筛选后的待办列表(使用computed结果<el-list class="todo-list" border :loading="todoStore.loading"><el-list-itemv-for="todo in filteredTodoList":key="todo.id"class="todo-item"<el-checkboxv-model="todo.completed"@change="(val) => todoStore.updateTodoStatus(todo.id, val)"><span :class="{ 'completed-text': todo.completed }</span></el-checkbox><el-buttontype="text"icon="Delete"class="delete-btn"@click="todoStore.deleteTodo(todo.id)"/></el-list-item><template #empty><el-empty description="暂无待办事项,快去新增吧!" /></template></el-list></div><script setup>import { ref, computed } from 'vue'import { useTodoStore } from '@/stores/todo'

const todoStore = useTodoStore ()const filterType = ref ('all') // 筛选类型:all/completed/active

// 用 computed 缓存筛选结果,只有 filterType 或 todoList 变化时才重新计算const filteredTodoList = computed (() => {switch (filterType.value) {case 'completed':return todoStore.todoList.filter (todo => todo.completed)case 'active':return todoStore.todoList.filter (todo => !todo.completed)default:return todoStore.todoList}})</script>

plaintext

复制代码
⚠️ 避坑提醒:不要在computed中写"副作用逻辑"(比如修改数据、发送请求),computed是"纯函数",只负责计算和缓存结果,副作用逻辑请放在methods或watch中。

### 2. 方案2:用v-once减少重复渲染(静态内容优化)
页面中有些内容是"静态的"(比如标题、固定提示文字),不会随数据变化而变化,但Vue默认会对这些内容进行"响应式监听",导致不必要的渲染。用`v-once`指令可标记静态内容,Vue会只渲染一次,后续不再监听和重新渲染。

实操:在`Home.vue`中给静态标题添加`v-once`:
```vue<template>
 <div class="home"><!-- 静态标题,添加v-once减少<h1 v-once>Vue 多页面应用首页</h1>
    <p v-once>基于Vue Router + Pinia 实战(性能优化版)</p>
    <p>当前待办数量:{{ todoStore.todo</p>
   <router-link to="/todo" class="btn">进入</router-link</div></template>

3. 方案 3:用 watch 监听优化响应逻辑(避免过度监听)

如果需要在数据变化时执行复杂逻辑(比如请求接口、操作 DOM),用watch监听指定数据,避免 "数据变化就触发逻辑"。Vue 3 的watch支持 "深度监听""立即执行""只监听指定属性" 等灵活配置,减少不必要的逻辑触发。

实操:在TodoDetail.vue中,监听路由参数变化时重新获取待办详情(避免页面复用导致数据不更新):<template><div class="todo-detail">

plaintext

复制代码
&#x3C;h2 v-once&#x3E;&#x3C;/h2&#x3C;div v-if=&#x22;todo&#x22; class=&#x22;todo-content&#x22;&#x3E;
  &#x3C;p&#x3E;{{ todo.title }}&#x3C;p&#x3E;&#x72B6;&#x6001;&#xFF1A;{{ todo.completed ? &#x27;&#x5DF2;&#x5B8C;&#x6210;&#x27; : &#x27;&#x672A;&#x5B8C;&#x6210;&#x27; }}&#x3C;el-button @click=&#x22;goBack&#x22;&#x3E;&#x3C;/el-button&#x3E;&#x3C;/div&#x3E;

<div v-else class="empty"><p>该待办</p><router-link to="/todo">返回待办</router-link></div></template<script setup>import { ref, watch } from 'vue'import { useRoute, useRouter } from 'vue-router'import { useTodoStore } from '@/stores/todo'import { getTodoDetail } from '@/api/todo'

const route = useRoute()const router = useRouter()const todoStore = useTodoStore()const todo = ref(null)

// 初始化获取详情const fetchTodoDetail = async (id) => {try {const res = await getTodoDetail (id)todo.value = {id: res.id,title: res.title,completed: res.completed}} catch (error) {todo.value = null}}

// 监听路由参数 id 的变化,重新获取详情(只监听 id,避免过度监听)watch (() => route.params.id, // 监听的目标:路由参数 id(newId) => {if (newId) fetchTodoDetail (newId) // 新 id 存在时才执行},{ immediate: true } // 立即执行一次(页面初始化时获取详情))

const goBack = () => {router.back()}</script>

plaintext

复制代码
## 四、核心实操三:大型列表优化------虚拟列表实战

如果待办列表数据量很大(比如1000+条),直接用`v-for`渲染会生成大量DOM节点,导致页面滚动卡顿、操作响应慢。虚拟列表是解决这个问题的最优方案------只渲染"当前视口可见的列表项",不管数据有多少,DOM节点数量始终保持在10-20个,大幅提升流畅度。

### 1. 实操:用vue-virtual-scroller实现虚拟列表
选择`vue-virtual-scroller`插件(Vue官方推荐,适配Vue 3),步骤如下:
1.  安装插件:`npm install vue-virtual-scroller -S`;
2.  在`main.js`中引入样式(必须引入,否则布局错乱):
```javascript
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
  1. TodoList.vue中使用虚拟列表替换原有的el-list

vue<template>

复制代码
 <div class="todo-list<!-- 其他内容不变,只修改列表渲染部分 -->
   <!-- 虚拟列表:height是列表容器高度,item-size是每个列表项高度(必须指定) -->
<RecycleScroller
      class="todo-virtual-list"
      :items="filteredTodoList"
      :item-size="50"
      height="500px"
    >
      <template #default="{ item }<!-- 列表项内容和之前一致 --><div class="todo-item">
          <el-checkbox 
            v-model="item.completed" 
            @change="(val) => todoStore.updateTodoStatus(item.id, val)"
          >
            <span :class="{ 'completed-text': item.completed }</span></el-checkbox><el-button 
            type="text" 
            icon="Delete" 
            class="delete-btn"
            @click="todoStore.deleteTodo(item.id)"
          />
        </template<!-- 空<template #empty>
        <el-empty description="暂无待办事项,快去新增吧!" />
      </RecycleScroller>
 </template<script setup>
import { ref, computed, onMounted } from 'vue'
import { useTodoStore } from '@/stores/todo'
// 引入虚拟列表组件
import { RecycleScroller } from 'vue-virtual-scroller'

const todoStore = useTodoStore()
const filterType = ref('all')

// 筛选后的待办列表(computed缓存不变)
const filteredTodoList = computed(() => {
  switch (filterType.value) {
    case 'completed':
      return todoStore.todoList.filter(todo => todo.completed)
    case 'active':
      return todoStore.todoList.filter(todo => !todo.completed)
    default:
      return todoStore.todoList
  }
})

// 页面加载时获取更多数据(模拟1000条数据,测试虚拟列表效果)
onMounted(() => {
  // 调用Pinia中的方法,获取1000条数据(需修改api/todo.js的getTodoList,去掉slice(0,10))
  todoStore.fetchTodoList()</script><style scoped>
/* 虚拟列表容器样式 */
.todo-virtual-list {
  width: 100%;
  border: 1px solid #eee;
  border-radius: 4px;
  margin: 16px 0;
}
/* 列表项样式不变 */
.todo-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 16px;
  height: 50px; /* 必须和item-size一致 */
}</style>

效果验证:修改api/todo.jsgetTodoList方法,去掉slice(0,10),获取 1000 条测试数据。启动项目后,滚动待办列表,会发现滚动流畅无卡顿,打开 Chrome 开发者工具→Elements 面板,查看 DOM 节点数量,始终保持在 20 个左右,优化效果显著。

五、综合实战:优化待办应用并验证性能提升

1. 实战目标

  1. 整合本节课所有优化方案:路由懒加载、图片优化、Element Plus 按需引入、computed 缓存、虚拟列表;
  2. 用 Lighthouse 测试优化前后的性能分数,验证优化效果(目标:性能分数从低于 80 提升到 90+);
  3. 测试用户体验:页面加载速度、列表滚动流畅度、操作响应速度是否明显提升。

2. 完整流程测试

  1. 本地执行npm run lint:fix,修复代码规范问题;
  2. 执行npm run build,查看优化后的打包体积(对比优化前减少 50% 以上);
  3. 用 Lighthouse 生成优化后的性能报告,查看分数提升(重点关注 "First Contentful Paint" 首次内容绘制、"Largest Contentful Paint" 最大内容绘制、"Interaction to Next Paint" 交互到下一次绘制);
  4. 部署优化后的项目到 Netlify,测试线上效果:
    • 页面加载:输入域名后,1-2 秒内完成加载,无白屏等待;
    • 列表滚动:1000 条待办数据滚动流畅,无卡顿;
    • 操作响应:点击筛选、新增、删除待办,响应及时,无延迟。

3. 新手优化建议

  1. 进阶优化:使用requestIdleCallback处理非紧急任务(比如统计数据上报),避免阻塞主线程;
  2. 性能监控:集成 Sentry(前端错误监控工具),实时监控线上应用的性能问题(比如卡顿、加载失败);
  3. 首屏优化:针对首屏添加 "骨架屏"(Element Plus 有现成的ElSkeleton组件),减少用户等待焦虑。

六、本节课总结与下节课预告

1. 本节课核心收获

  • 加载性能优化:掌握路由懒加载、图片优化、第三方依赖按需引入,减少资源体积和请求数量;
  • 运行时优化:掌握 computed 缓存、v-once、watch 监听优化,减少不必要的组件渲染和重复计算;
  • 大型列表优化:掌握虚拟列表的使用,解决大量数据渲染的卡顿问题;
  • 优化思维:理解 "先测后改" 的核心原则,学会用工具定位性能瓶颈,避免无效优化。

2. 课后作业(必做)

  1. 独立完成待办应用的所有性能优化,对比优化前后的打包体积和加载速度;
  2. 用 Lighthouse 生成优化前后的两份性能报告,分析分数差异和优化点;
  3. 实现 "骨架屏" 功能,在待办列表加载时显示骨架屏,优化首屏体验;
  4. 整理性能优化踩坑笔记,比如 "虚拟列表 item-size 设置错误导致布局错乱""按需引入插件配置错误导致组件失效" 等。

3. 下节课预告

下节课我们将学习 "Vue 3 项目实战拓展 ------ 从待办到全功能任务管理系统",整合前面所有课程的知识,新增用户登录、任务分类、数据统计、权限控制等企业级功能,带你完成一个 "可放入简历的完整项目",同时学习项目复盘和简历撰写技巧,为你的前端求职铺路!

相关推荐
寻星探路4 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端