代码改变世界

vue3研发过程中的性能优化

2025-12-12 12:36  tlnshuju  阅读(0)  评论(0)    收藏  举报

1. 组件级别优化

1.1.懒加载

1.1.1 组件懒加载

<template>
  <div>
    <h1>我的应用</h1>
      <AsyncComponent />
        </div>
          </template>
            // 使用 defineAsyncComponent
            import { defineAsyncComponent } from 'vue'
            const AsyncComp = defineAsyncComponent(() =>
            import('./components/AsyncComponent.vue')
            )
            // 或者带配置的懒加载
            const AsyncCompWithOptions = defineAsyncComponent({
            loader: () => import('./HeavyComponent.vue'),
            loadingComponent: LoadingComponent,
            errorComponent: ErrorComponent,
            delay: 200,
            timeout: 3000
            })

组件懒加载的好处:

  1. 显著提升首屏加载性能
    只加载当前页面需要的组件,减少初始包体积,加快首屏渲染速度,降低初始 JavaScript 解析和执行时间
  2. 优化资源利用率
    按需加载,避免一次性下载所有组件代码。减少不必要的网络请求和内存占用。特别适合大型应用和复杂页面
  3. 代码分割和优化
    自动进行代码分割,生成独立的 chunk。便于浏览器缓存和后续加载优化。配合预加载和预获取进一步优化性能

1.1.2 路由组件懒加载

同步加载路由,应用初始化时一次性加载所有路由组件

import About from '@/views/About.vue'
import Contact from '@/views/Contact.vue'
const routes = [
{
path: '/about',
name: 'About',
component: About
},
{
path: '/contact',
name: 'Contact',
component: Contact
}
]

懒加载路由,用户访问对应路由时才加载相关组件,实现代码分割

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
// 首页使用正常加载(因为首页通常是必须的)
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
// 关于页面使用懒加载
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
},
// 用户页面使用懒加载并分组
{
path: '/user/:id',
name: 'User',
component: () => import(/* webpackChunkName: "user" */ '@/views/User.vue')
},
// 管理后台相关页面分组打包
{
path: '/admin',
name: 'Admin',
component: () => import(/* webpackChunkName: "admin" */ '@/views/Admin.vue'),
children: [
{
path: 'dashboard',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/Dashboard.vue')
},
{
path: 'settings',
component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/Settings.vue')
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

1.2 函数式组件(适用于简单场景)

import { h } from 'vue'
const FunctionalButton = (props, { slots }) => {
return h('button', {
onClick: props.onClick,
class: 'btn'
}, slots.default?.())
}

性能优化:无实例化开销
代码简洁:适合简单展示逻辑
可预测性:纯函数特性
易于测试:无需模拟组件实例

2. 响应式系统优化

2.1 合理使用响应式API

import { ref, shallowRef, markRaw } from 'vue'
// 对于不需要深度响应的大对象,使用 shallowRef
const largeObject = shallowRef({ ... })
// 标记不需要响应式的对象
const staticConfig = markRaw({
version: '1.0.0',
apiUrl: 'https://api.example.com'
})
// 避免不必要的响应式
const nonReactiveArray = [...]

2.2 计算属性缓存

import { computed } from 'vue'
// 好的实践:使用计算属性缓存计算结果
const expensiveValue = computed(() => {
return heavyCalculation(props.data)
})
// 避免在模板中直接调用方法
// 不推荐
{{ calculateSomething() }}
// 推荐使用声明的计算属性
{{ computedSomething }}
const user = ref({
profile: {
personal: {
name: 'John',
age: 25,
address: {
city: 'Beijing',
street: 'Main St'
}
}
}
})
//  过度依赖 - 任何嵌套属性变化都会触发重新计算,要避免嵌套
const badUserInfo = computed(() => {
return `${user.value.profile.personal.name} - ${user.value.profile.personal.age}`
})
// 精确依赖 - 只依赖实际需要的属性
const goodUserInfo = computed(() => {
const personal = user.value.profile.personal
return `${personal.name} - ${personal.age}`
})
// 使用解构进一步优化
const optimizedUserInfo = computed(() => {
const { name, age } = user.value.profile.personal
return `${name} - ${age}`
})

-当计算属性的依赖不变时 计算属性多次访问只计算一次

  • 计算属性的依赖元素的任何嵌套属性变化都会触发重新计算,不要过度依赖
  • 大数据集合使用链式操会出现性能问题 largeDataset.filter() .map() .reduce()
  • .filter() // 创建新数组1
  • .map() // 创建新数组2
  • .reduce() // 最终结果

3. 渲染优化

3.1 使用 v-once 和 v-memo

<template>
  <!-- 静态内容只渲染一次 -->
    <div v-once>{{ staticContent }}</div>
      <!-- 根据依赖记忆子树 只有依赖数组中的值变化时才会重新渲染 -->
        <div v-memo="[valueA, valueB]">
          {{ valueA }} - {{ valueB }}
          </div>
            </template>

v-once 适用场景:
真正静态的内容(条款、说明、配置信息),复杂的静态模板结构,初始渲染后永不变化的数据展示;
v-memo 适用场景:
大型列表渲染优化,复杂组件的选择性重渲染,表单中独立区块的优化,虚拟滚动中的可见项
选择原则:
完全静态 → v-once
有条件更新 → v-memo
频繁更新 → 不使用特殊指令

3.2 合理使用 key

key 本身不会直接提升性能,它的主要作用是保证正确性。在某些情况下,正确使用 key 可以间接避免性能问题,但使用不当反而会降低性能。

<template>
  <!-- 列表渲染使用合适的 key -->
    <div v-for="item in list" :key="item.id">
      {{ item.name }}
      </div>
        <!-- 条件渲染时使用 key,当key变化是会强制重新渲染 -->
          <component
          :is="currentComponent"
          :key="componentKey"
          />
          <!-- 当不使用key时出现的问题:输入框内容会被保留 -->
            <div v-if="isEditing">
              <input v-model="content" placeholder="编辑内容">
                </div>
                  <div v-else>
                    <input v-model="content" placeholder="查看内容">
                      </div>
                        </template>

提升性能 - 高效的 DOM 复用:
减少不必要的 DOM 操作,避免重复创建和销毁组件实例,保持已有的 DOM 状态(如表单输入、滚动位置)
key值使用唯一标识避免出现重复,避免使用不稳定key
推荐使用场景:动态组件切换,条件渲染表单,列表渲染

状态保留指的是当 Vue 复用 DOM 元素或组件时,元素/组件的内部状态被意外保留的情况。
状态保留的具体表现:输入框内容保留,表单验证状态保留(错误提示信息可能在表单切换时意外显示),组件内部状态保留(状态和输入内容会被保留,生命周期钩子也不会重新触发),动态组件状态保留,列表渲染中的状态错位(排序后第一行第一列数据与第二行第二例组合为一条数据)

状态保留的根本原因:Vue 通过比较虚拟 DOM 来决定如何更新真实 DOM。当它发现两个元素相似时,会选择复用而不是重新创建。
key 告诉 Vue"这两个看起来相似的元素实际上是不同的"。

4. 事件处理优化

4.1 事件处理器防抖

import { ref, watch } from 'vue'
import { debounce } from 'lodash-es'
const searchQuery = ref('')
// 使用防抖的观察器
watch(
searchQuery,
debounce((newQuery) => {
searchAPI(newQuery)
}, 300)
)
// 有防抖 - 只在停止输入后触发,减少不必要的函数执行
const debouncedSearch = debounce((event) => {
console.log('搜索:', event.target.value);
// 只在用户停止输入后执行一次
}, 300);

为什么防抖能提升性能?

  • 减少不必要的函数执行
  • 减少 DOM 更新和重渲染

使用场景:搜索框输入, 窗口大小调整,滚动事件

Debounce 与 Throttle 的区别:

特性Throttle(节流)Debounce(防抖)
执行时机固定时间间隔执行停止触发后延迟执行
执行次数均匀分布执行只执行最后一次
适用场景实时性要求高最终结果重要

Throttle 应用: 滚动事件处理,实时搜索建议(需要即时反馈),鼠标移动跟踪, 调整窗口大小

4.2 事件代理

事件代理:在父元素上绑定一个事件监听器,利用事件冒泡机制处理所有子元素的事件

<template>
  <!-- 使用事件代理处理大量子元素 -->
    <div @click="handleItemClick">
      <div v-for="item in items" :data-id="item.id">
        {{ item.name }}
        </div>
          </div>
            </template>
              <script setup>
                function handleItemClick(event) {
                const id = event.target.dataset.id
                if (id) {
                // 处理点击
                }
                }
                </script>

适合事件代理:

'click',        // 点击事件
'dblclick',     // 双击事件
'contextmenu',  // 右键菜单
'change',       // 表单变化(需要冒泡)
'input'         // 输入事件(需要冒泡)

不适合事件代理的事件:

focus',        // 焦点事件(不冒泡)
'blur',         // 失去焦点(不冒泡)
'load',         // 加载事件
'error'         // 错误事件

事件代理在 Vue 3 中的优势:

  • 大幅减少内存使用 - 从 N 个事件监听器减少到 1 个
  • 提升初始化性能 - 避免大量的事件绑定操作
  • 简化动态内容处理 - 新添加的元素自动拥有事件处理
  • 更好的代码组织 - 集中管理事件逻辑
  • 减少 GC 压力 - 更少的事件监听器意味着更少的垃圾回收

适用场景:

  • 大型列表或表格
  • 动态内容频繁变化的界面
  • 具有大量交互元素的复杂组件
  • 需要高性能渲染的虚拟列表

5. 状态管理优化

5.1 使用 Pinia 进行状态管理

Pinia 本身不会自动让应用更快,而是提供了优化工具和模式,帮助开发者避免性能陷阱,实现高效状态管理。

// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})

pinia状态管理性能检查清单:

  • Store 按功能模块分割
  • 使用 storeToRefs() 解构状态
  • Getters 进行缓存和优化
  • Actions 批量处理状态更新
  • 大数据集按需加载和清理
  • 持久化只保存必要字段

5.2 状态分割

状态分割主要通过缩小响应式更新的影响范围和优化组件重新渲染来实现性能提升

// 将大状态分割成小store
export const useUserStore = defineStore('user', {
state: () => ({
profile: null,
preferences: {}
})
})
export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
total: 0
})
})

状态分割是对状态管理的优化。具体提成在:
减少不必要的组件重新渲染
优化计算属性依赖追踪,分割后计算属性精准依赖,减少计算次数
更细粒度的响应式更新

6. 打包优化

6.1 Vite 配置优化

// vite.config.js
export default {
build: {
// 代码分割
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'ui-library': ['element-plus', 'lodash-es']
}
}
},
// 压缩优化
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
},
// 依赖预构建
optimizeDeps: {
include: ['lodash-es', 'axios']
}
}

6.2 组件库按需导入

// 对于 Element Plus
import { createApp } from 'vue'
import { ElButton, ElInput } from 'element-plus'
const app = createApp(App)
app.use(ElButton)
app.use(ElInput)

7. 内存优化

及时清理副作用

7.1 移除定时器

import { onUnmounted, watchEffect } from 'vue'
export function useTimer() {
let timerId
//页面卸载前清除startTimer 定时器
onUnmounted(() => {
if (timerId) {
clearInterval(timerId)
}
})
const startTimer = () => {
timerId = setInterval(() => {
// do something
}, 1000)
}
return { startTimer }
}

7.2 避免内存泄漏,移除监听器和事件监听器

// 在组合式函数中清理监听器和事件监听器
export function useEventListener(target, event, callback) {
//挂载时添加了事件监听器
onMounted(() => target.addEventListener(event, callback))
//页面卸载前移除事件监听器
onUnmounted(() => target.removeEventListener(event, callback))
}

8. 网络请求优化

8.1 请求缓存

import { ref } from 'vue'
const cache = new Map()
export function useCachedFetch(url) {
const data = ref(null)
const loading = ref(false)
if (cache.has(url)) {
data.value = cache.get(url)
return { data, loading }
}
loading.value = true
fetch(url)
.then(res => res.json())
.then(json => {
data.value = json
cache.set(url, json)
})
.finally(() => loading.value = false)
return { data, loading }
}

9. 开发工具优化

9.1 使用 Vue DevTools

Vue DevTools 能显著帮助提升 Vue 3 应用的性能,但它本身是一个开发工具,不会直接提升运行时性能。它的价值在于提供深度洞察和诊断能力,帮助你发现、分析和解决性能问题。

// 生产环境禁用 DevTools
if (process.env.NODE_ENV === 'production') {
app.config.devtools = false
app.config.performance = false
}