Vue3 性能优化规范:日常必做优化(不玄学、可落地)|可维护性与兜底规范篇

【Vue3 + 中后台开发】:从基础响应式到工程化优化,掌握日常必做性能规范,避开渲染与维护高频坑!

📑 文章目录

  • 一、前言:我们为什么要写这篇规范
  • 二、基础扫盲:先搞清楚这些概念
    • [2.1 computed 和 watch 怎么选?](#2.1 computed 和 watch 怎么选?)
    • [2.2 v-if 和 v-show 怎么选?](#2.2 v-if 和 v-show 怎么选?)
    • [2.3 ref 和 reactive 怎么选?](#2.3 ref 和 reactive 怎么选?)
    • [2.4 :key 必须认真写](#2.4 :key 必须认真写)
  • 三、日常必做优化清单
    • [3.1 列表渲染:减少不必要的子组件](#3.1 列表渲染:减少不必要的子组件)
    • [3.2 事件处理器:避免内联创建函数](#3.2 事件处理器:避免内联创建函数)
    • [3.3 合理使用 v-once 和 v-memo](#3.3 合理使用 v-once 和 v-memo)
    • [3.4 大组件拆分 + defineOptions](#3.4 大组件拆分 + defineOptions)
  • [四、进阶实战:路由懒加载与 Keep-Alive](#四、进阶实战:路由懒加载与 Keep-Alive)
    • [4.1 路由懒加载(必做)](#4.1 路由懒加载(必做))
    • [4.2 Keep-Alive 缓存页面状态](#4.2 Keep-Alive 缓存页面状态)
  • 五、可维护性与兜底规范
    • [5.1 兜底:Loading / 空态 / 错误](#5.1 兜底:Loading / 空态 / 错误)
    • [5.2 兜底:接口超时与重试](#5.2 兜底:接口超时与重试)
    • [5.3 兜底:大列表用虚拟滚动](#5.3 兜底:大列表用虚拟滚动)
    • [5.4 兜底:图片懒加载](#5.4 兜底:图片懒加载)
  • 六、避坑总结
  • 七、小结
  • [🔍 系列模块导航](#🔍 系列模块导航)
    • [📝 可维护性与兜底规范](#📝 可维护性与兜底规范)
    • [📚 系列总览](#📚 系列总览)

同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。

(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)

很多前端开发者都会遇到一个瓶颈:

代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。

想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验

这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。

帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。


一、前言:我们为什么要写这篇规范

工作几年后会发现,很多「性能优化」文章要么讲得特别底层,要么只给一两行代码。本文的目的很简单:让你在日常写 Vue3 时,知道该选什么、为什么选,以及容易踩的坑

适合人群:

  • 会写 JS,但 computedwatchref 有点混
  • 技术小白,想从零开始打好基础
  • 有经验的前端,想系统梳理和校准自己的习惯

本文按「日常必做」→「进阶实战」→「可维护性与兜底」来写,力求可落地。

[⬆ 返回目录](#⬆ 返回目录)


二、基础扫盲:先搞清楚这些概念

2.1 computed 和 watch 怎么选?

简单记:

  • 需要同步算出新值 → 用 computed
  • 需要做副作用 (发请求、打日志等)→ 用 watch
html 复制代码
<script setup>
import { ref, computed, watch } from 'vue'

const keyword = ref('')
const list = ref([
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' },
  { id: 3, name: '橙子' },
])

// ✅ computed:纯计算,依赖变化自动重新算
const filteredList = computed(() => {
  return list.value.filter(item => 
    item.name.includes(keyword.value)
  )
})

// ✅ watch:需要副作用时用
watch(keyword, (newVal) => {
  console.log('搜索关键词变了:', newVal)
  // 比如这里可以调接口、埋点等
})
</script>

<template>
  <input v-model="keyword" placeholder="搜索" />
  <ul>
    <li v-for="item in filteredList" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

常见坑:

  • watch 去算一个「展示用的派生值」→ 应换成 computed,更符合 Vue 的响应式设计,也更好维护
  • computed 里写 fetchlocalStorage 等副作用 → 应放到 watchonMounted

[⬆ 返回目录](#⬆ 返回目录)

2.2 v-if 和 v-show 怎么选?

一句话:

条件切换频繁v-show初始就不常出现v-if

对比项 v-if v-show
原理 条件为 false 时不渲染 始终渲染,用 CSS display 控制
切换成本 高(频繁创建/销毁)
初始渲染 不渲染的节点不占内存 会渲染,占内存
html 复制代码
<template>
  <!-- ✅ 弹窗、Tab 等频繁切换 → v-show -->
  <div v-show="isModalOpen" class="modal">弹窗内容</div>

  <!-- ✅ 权限、一次性条件 → v-if -->
  <AdminPanel v-if="user.role === 'admin'" />
</template>

常见坑: 大量列表项里用 v-if 做过滤 → 应先在数据层过滤(如用 computed),再 v-for 渲染,而不是在模板里 v-if

[⬆ 返回目录](#⬆ 返回目录)

2.3 ref 和 reactive 怎么选?

建议:普通场景多用 ref

  • ref:适合基础类型、单个对象,解构不会丢响应式
  • reactive:适合对象整体是「可变的配置」,解构会丢响应式
js 复制代码
import { ref, reactive } from 'vue'

// ✅ ref:适合大部分场景
const count = ref(0)
const user = ref({ name: '张三', age: 18 })

// reactive:解构会丢失响应式!
const state = reactive({ count: 0 })
const { count } = state  // ❌ count 不再响应式

[⬆ 返回目录](#⬆ 返回目录)

2.4 :key 必须认真写

v-forkey 直接影响虚拟 DOM 的 diff 和复用。

原则:稳定、唯一 的 id,不要用 index

html 复制代码
<!-- ❌ index 会随排序、增删变化,导致错乱或多余渲染 -->
<li v-for="(item, index) in list" :key="index">{{ item.name }}</li>

<!-- ✅ 用唯一 id -->
<li v-for="item in list" :key="item.id">{{ item.name }}</li>

常见坑: 接口没返回 id 时,可用 item.xxx + index 做临时 key,但上线前应改为真实 id。

[⬆ 返回目录](#⬆ 返回目录)


三、日常必做优化清单

3.1 列表渲染:减少不必要的子组件

html 复制代码
<!-- ❌ 每次都创建新组件实例 -->
<div v-for="item in list" :key="item.id">
  <HeavyChild :data="item" />
</div>

<!-- ✅ 简单展示用内联,避免多余组件 -->
<div v-for="item in list" :key="item.id">
  <span>{{ item.name }}</span>
  <span>{{ item.price }}</span>
</div>

规则: 只有在有独立逻辑、状态、插槽或需要被复用的时候,再拆成组件。

[⬆ 返回目录](#⬆ 返回目录)

3.2 事件处理器:避免内联创建函数

html 复制代码
<!-- ❌ 每次渲染都创建新函数 -->
<button @click="() => doSomething(item.id)">点击</button>

<!-- ✅ 稳定引用 -->
<button @click="handleClick(item.id)">点击</button>
js 复制代码
const handleClick = (id) => {
  doSomething(id)
}

原因: 内联箭头函数会随渲染不断新建,增加子组件不必要的更新。

[⬆ 返回目录](#⬆ 返回目录)

3.3 合理使用 v-once 和 v-memo

html 复制代码
<!-- 静态内容只渲染一次 -->
<p v-once>{{ staticTitle }}</p>

<!-- v-memo:依赖不变就不更新(Vue 3.2+)-->
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.name]">
  {{ item.name }} - {{ item.price }}
</div>

v-memo 适合:列表项结构简单,但数据量大的场景。

[⬆ 返回目录](#⬆ 返回目录)

3.4 大组件拆分 + defineOptions

大组件难以维护,也影响局部更新。建议按「区块」拆分,并用 defineOptions 明确 name

html 复制代码
<!-- UserProfile.vue -->
<script setup>
defineOptions({
  name: 'UserProfile',
})
</script>

<template>
  <div class="user-profile">
    <UserAvatar />
    <UserInfo />
    <UserStats />
  </div>
</template>

[⬆ 返回目录](#⬆ 返回目录)


四、进阶实战:路由懒加载与 Keep-Alive

4.1 路由懒加载(必做)

js 复制代码
// ❌ 同步加载:首屏会加载所有路由
const routes = [
  { path: '/home', component: Home },
  { path: '/about', component: About },
]

// ✅ 懒加载:按需加载
const routes = [
  { path: '/home', component: () => import('@/views/Home.vue') },
  { path: '/about', component: () => import('@/views/About.vue') },
]

这样首屏只加载当前路由对应的 chunk,其它路由在访问时才加载。

[⬆ 返回目录](#⬆ 返回目录)

4.2 Keep-Alive 缓存页面状态

html 复制代码
<!-- App.vue 或布局组件 -->
<template>
  <router-view v-slot="{ Component }">
    <keep-alive :include="['Home', 'List']">
      <component :is="Component" />
    </keep-alive>
  </router-view>
</template>

include 要和组件的 name 一致。被缓存的页面再次进入时不会重新创建,适合列表滚动位置、表单填写等场景。

[⬆ 返回目录](#⬆ 返回目录)


五、可维护性与兜底规范

5.1 兜底:Loading / 空态 / 错误

html 复制代码
<template>
  <div v-if="loading" class="loading">加载中...</div>
  <div v-else-if="error" class="error">
    {{ error }}
    <button @click="retry">重试</button>
  </div>
  <div v-else-if="!list.length" class="empty">暂无数据</div>
  <div v-else>
    <ul>
      <li v-for="item in list" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script setup>
const loading = ref(true)
const error = ref(null)
const list = ref([])

const fetchData = async () => {
  loading.value = true
  error.value = null
  try {
    const res = await api.getList()
    list.value = res.data
  } catch (e) {
    error.value = e.message || '加载失败'
  } finally {
    loading.value = false
  }
}

const retry = () => fetchData()

onMounted(fetchData)
</script>

这样用户在任何状态下都能得到明确反馈。

[⬆ 返回目录](#⬆ 返回目录)

5.2 兜底:接口超时与重试

js 复制代码
const fetchWithTimeout = (url, options = {}, timeout = 5000) => {
  const controller = new AbortController()
  const id = setTimeout(() => controller.abort(), timeout)

  return fetch(url, { ...options, signal: controller.signal })
    .finally(() => clearTimeout(id))
}

生产环境建议配合业务封装统一请求库,做超时、重试、错误上报。

[⬆ 返回目录](#⬆ 返回目录)

5.3 兜底:大列表用虚拟滚动

当列表有几百、上千条时,只渲染可视区域:

bash 复制代码
npm install vue-virtual-scroller
html 复制代码
<template>
  <RecycleScroller
    :items="list"
    :item-size="50"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="item">{{ item.name }}</div>
  </RecycleScroller>
</template>

[⬆ 返回目录](#⬆ 返回目录)

5.4 兜底:图片懒加载

html 复制代码
<img v-lazy="imageUrl" alt="" />
js 复制代码
// main.js 或插件
app.directive('lazy', {
  mounted(el, binding) {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        el.src = binding.value
      }
    })
    observer.observe(el)
  },
})

[⬆ 返回目录](#⬆ 返回目录)


六、避坑总结

场景 错误做法 正确做法
派生数据 用 watch 计算展示用数据 用 computed
条件渲染 频繁切换用 v-if 用 v-show
列表 key 用 index 用唯一 id
事件 内联箭头函数 抽成方法
路由 同步 import 懒加载 import
大列表 直接全部渲染 虚拟滚动或分页
接口 无 loading/error 加 loading、error、重试

[⬆ 返回目录](#⬆ 返回目录)

七、小结

性能优化不需要追求极端,重点是:

  1. 概念清晰:computed / watch、v-if / v-show、ref / reactive 会用、会用对
  2. 日常习惯:正确 key、避免内联函数、合理拆组件
  3. 必做项:路由懒加载、图片懒加载、大列表虚拟滚动
  4. 可维护与兜底:Loading、空态、错误处理、超时重试

这些规范在日常开发中都能直接落地,不会有太玄学的内容。建议先按本文做一轮自检,再结合实际项目逐步迭代优化。


🔍 系列模块导航

📝 可维护性与兜底规范

一、《Vue 中后台项目可维护性规范:代码分层 + 逻辑拆分 + 数据兜底,3 年仍可维护|可维护性与兜底规范篇》

二、《Vue3 性能优化规范:日常必做优化(不玄学、可落地)|可维护性与兜底规范篇》
三、《Vue3 + 组合式 API 前端异常兜底规范:接口失败 / 无数据 / 页面异常统一处理|可维护性与兜底规范篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

📚 系列总览

前端体系化学习完全体:基础 → 规范 → 架构 → 大厂面试

四套系列、百余篇高质量实战文,从入门到进阶,一站式补齐前端核心能力

每个系列完结后,都会整理成一篇完整导航文并附上直达链接,方便大家按顺序、体系化学习。

全套内容持续更新中,敬请期待~

[⬆ 返回目录](#⬆ 返回目录)


技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护

哪怕每次只吃透一条规范,长期下来,差距会非常明显。

后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。

如果你觉得这篇文章对你有帮助,欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。

我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~

相关推荐
Binary-Jeff2 小时前
Spring 创建 Bean 的关键流程
java·开发语言·前端·spring boot·后端·spring·学习方法
Frank_refuel2 小时前
QT->信号与槽详解上(概述、使用、自定义、连接方式、其他说明)
开发语言·qt
深耕AI2 小时前
【VS Code 中 Python 虚拟环境降级完整指南(含 uv 工具实战)】
开发语言·python·uv
若年封尘2 小时前
告别手写 API 类型:用 openapi-fetch 打造类型安全的前端接口层
前端·安全·openapi-fetch
cypking2 小时前
二次封装ElementUI日期范围组件:打造带限制规则的Vue2 v-model响应式通用组件
前端·javascript·elementui
A923A2 小时前
【小兔鲜电商前台 | 项目笔记】第二天
前端·vue.js·笔记·项目·小兔鲜
牧码岛2 小时前
Web前端之样式中的light-dark函数,从媒体查询到颜色函数,从颜色到图片,light-dark打开CSS新时代、主题切换的暗黑模式到image的正解
前端·css·web·web前端
小CC吃豆子2 小时前
C/C++中 int 的最大最小值
c语言·开发语言·c++
Bert.Cai2 小时前
pymysql自动提交设置
开发语言·python