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

相关推荐
向下的大树2 小时前
React 环境搭建 + 完整 Demo 教程
前端·react.js·前端框架
2501_916007472 小时前
React Native 混淆在真项目中的方式,当 JS 和原生同时暴露
javascript·react native·react.js·ios·小程序·uni-app·iphone
IT_陈寒2 小时前
Python性能翻倍的5个隐藏技巧:让你的代码跑得比同事快50%
前端·人工智能·后端
Можно2 小时前
GET与POST深度解析:区别、适用场景与dataType选型指南
前端·javascript
爱上妖精的尾巴2 小时前
5-41 WPS JS宏 数组迭代基础测试与双数组迭代的使用方法测试
前端·javascript·wps
Tisfy3 小时前
“豆包聊天搜索” —— 直接在Chrome等浏览器地址栏开启对话
前端·chrome·豆包
Data_agent3 小时前
京东商品价格历史信息API使用指南
java·大数据·前端·数据库·python
weixin_440730503 小时前
HTML中的css和js的书写样式
javascript·css·html
大学生资源网3 小时前
基于Vue的网上购物管理系统的设计与实现(java+vue+源码+文档)
java·前端·vue.js·spring boot·后端·源码