Vue 性能优化实战指南

23 阅读3分钟

引言

随着 Vue 应用的规模不断扩大,性能问题逐渐显现:长列表渲染卡顿、首屏加载缓慢、组件重复渲染... 本文将深入探讨 Vue 性能优化的两大核心策略:虚拟列表异步组件,帮助你打造流畅的用户体验。

一、虚拟列表:解决长列表渲染瓶颈

问题场景

当列表包含数千条数据时,直接渲染所有 DOM 节点会导致:

  • 页面滚动卡顿
  • 内存占用过高
  • 首次渲染时间过长

解决方案

虚拟列表的核心思想:只渲染可视区域内的元素

实现原理

虚拟列表通过计算可视区域,只渲染用户可见的列表项,大大减少 DOM 节点数量。

代码示例

<template>
  <div class="virtual-list" ref="listContainer">
    <div 
      class="placeholder" 
      :style="{ height: totalHeight + 'px' }"
    >
      <div
        v-for="item in visibleItems"
        :key="item.id"
        class="list-item"
        :style="{ 
          position: 'absolute',
          top: item.offset + 'px'
        }"
      >
        {{ item.content }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'

const props = defineProps({
  data: { type: Array, required: true },
  itemHeight: { type: Number, default: 50 },
  bufferSize: { type: Number, default: 5 }
})

const listContainer = ref(null)
const scrollTop = ref(0)

// 计算可视区域
const visibleItems = computed(() => {
  if (!listContainer.value) return []
  
  const containerHeight = listContainer.value.clientHeight
  const startIndex = Math.floor(scrollTop.value / props.itemHeight)
  const visibleCount = Math.ceil(containerHeight / props.itemHeight)
  
  const start = Math.max(0, startIndex - props.bufferSize)
  const end = Math.min(props.data.length, start + visibleCount + props.bufferSize * 2)
  
  return props.data.slice(start, end).map((item, index) => ({
    ...item,
    offset: (start + index) * props.itemHeight
  }))
})

const totalHeight = computed(() => 
  props.data.length * props.itemHeight
)

const handleScroll = (e) => {
  scrollTop.value = e.target.scrollTop
}

onMounted(() => {
  listContainer.value?.addEventListener('scroll', handleScroll)
})

onUnmounted(() => {
  listContainer.value?.removeEventListener('scroll', handleScroll)
})
</script>

<style scoped>
.virtual-list {
  height: 600px;
  overflow-y: auto;
  position: relative;
}

.placeholder {
  position: relative;
}

.list-item {
  height: 50px;
  padding: 10px;
  border-bottom: 1px solid #eee;
}
</style>

使用现成库

推荐使用成熟的虚拟列表库:

npm install vue-virtual-scroller
<template>
  <RecycleScroller
    :items="largeList"
    :item-size="50"
    key-field="id"
  >
    <template #default="{ item }">
      <div class="item">{{ item.name }}</div>
    </template>
  </RecycleScroller>
</template>

二、异步组件:优化首屏加载

问题场景

大型应用中,所有组件打包在一个 bundle 中,导致:

  • 首屏加载时间过长
  • 资源浪费(用户可能不会访问某些页面)

解决方案

代码分割 + 懒加载,只加载当前需要的组件。

基础用法

// 传统方式 - 同步导入
import HeavyComponent from './HeavyComponent.vue'

// 优化方式 - 异步导入
const HeavyComponent = defineAsyncComponent(() => 
  import('./HeavyComponent.vue')
)

带加载状态的异步组件

<script setup>
import { defineAsyncComponent } from 'vue'

// 带 loading 和 error 状态的异步组件
const AsyncComponent = defineAsyncComponent({
  loader: () => import('./HeavyComponent.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorFallback,
  delay: 200, // 200ms 后显示 loading
  timeout: 3000 // 3 秒超时
})
</script>

<template>
  <AsyncComponent />
</template>

路由级别的代码分割

// router/index.js
const routes = [
  {
    path: '/',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/admin',
    component: () => import('@/views/Admin.vue'),
    meta: { requiresAuth: true }
  },
  {
    path: '/settings',
    component: () => import('@/views/Settings.vue')
  }
]

按需加载大型依赖

// 不推荐 - 导入整个库
import lodash from 'lodash'

// 推荐 - 按需导入
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'

// 或使用 ES Module 语法
import { debounce, throttle } from 'lodash-es'

三、其他性能优化技巧

1. 使用 v-memo 缓存组件

Vue 3.2+ 引入的 v-memo 可以缓存组件渲染:

<template>
  <div v-memo="[valueA, valueB]">
    {{ valueA }} + {{ valueB }}
  </div>
</template>

2. 避免不必要的响应式

// 对于不需要响应式的大对象,使用 shallowRef
import { shallowRef } from 'vue'

const largeData = shallowRef(null)

// 只在需要时触发更新
const loadData = async () => {
  largeData.value = await fetchLargeData()
}

3. 列表使用 key 优化

<!-- 不推荐 -->
<li v-for="(item, index) in list" :key="index">
  {{ item.name }}
</li>

<!-- 推荐 -->
<li v-for="item in list" :key="item.id">
  {{ item.name }}
</li>

四、性能监控

使用 Vue DevTools 和性能工具:

// 性能监控
const start = performance.now()
// ... 执行操作
const end = performance.now()
console.log(`耗时:${end - start}ms`)

// 或使用 Vue 的 performance API
if (__VUE_PROD__) {
  performance.mark('component-mount-start')
  // ...
  performance.mark('component-mount-end')
  performance.measure('component-mount', 
    'component-mount-start', 
    'component-mount-end'
  )
}

总结

Vue 性能优化的核心原则:

  1. 减少渲染量 - 虚拟列表、分页加载
  2. 延迟加载 - 异步组件、路由懒加载
  3. 避免重复计算 - v-memo、computed 缓存
  4. 按需引入 - 减少 bundle 体积

记住:过早优化是万恶之源。先确保功能正确,再根据实际性能数据进行优化。