Vue 项目优化实践

Vue 项目中常用的性能优化方法和代码示例

问题

在实际工作中,你对 Vue 做过哪些优化?

解答

1. 代码层面优化

v-if 与 v-show 的选择

<template>
  <!-- 频繁切换用 v-show,只是 CSS 切换 -->
  <div v-show="isVisible">频繁切换的内容</div>
  
  <!-- 条件很少改变用 v-if,真正的条件渲染 -->
  <HeavyComponent v-if="isLoaded" />
</template>

v-for 使用唯一 key

<template>
  <!-- 使用唯一标识作为 key,避免使用 index -->
  <div v-for="item in list" :key="item.id">
    {{ item.name }}
  </div>
</template>

computed 缓存计算结果

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

const list = ref([1, 2, 3, 4, 5])

// computed 会缓存结果,依赖不变时不会重新计算
const total = computed(() => {
  console.log('计算 total')
  return list.value.reduce((sum, n) => sum + n, 0)
})
</script>

使用 keep-alive 缓存组件

<template>
  <!-- 缓存组件状态,避免重复渲染 -->
  <keep-alive :include="['Home', 'List']" :max="10">
    <router-view />
  </keep-alive>
</template>

2. 路由懒加载

// router/index.js
const routes = [
  {
    path: '/home',
    // 动态导入,按需加载
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    // 指定 chunk 名称,便于调试
    component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
  }
]

3. 组件异步加载

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

// 异步加载重型组件
const HeavyChart = defineAsyncComponent({
  loader: () => import('./HeavyChart.vue'),
  loadingComponent: LoadingSpinner,
  delay: 200,
  timeout: 3000
})
</script>

4. 虚拟滚动处理长列表

<template>
  <!-- 使用 vue-virtual-scroller 处理大数据列表 -->
  <RecycleScroller
    :items="bigList"
    :item-size="50"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="item">{{ item.name }}</div>
  </RecycleScroller>
</template>

<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

// 假设有 10000 条数据
const bigList = ref([...])
</script>

5. 图片懒加载

<template>
  <!-- 使用 loading="lazy" 原生懒加载 -->
  <img 
    v-for="img in images" 
    :key="img.id"
    :src="img.url" 
    loading="lazy"
    alt=""
  />
</template>

<!-- 或使用 v-lazy 指令 -->
<template>
  <img v-lazy="img.url" />
</template>

6. 防抖节流

<script setup>
import { useDebounceFn, useThrottleFn } from '@vueuse/core'

// 搜索输入防抖
const handleSearch = useDebounceFn((keyword) => {
  fetchSearchResult(keyword)
}, 300)

// 滚动事件节流
const handleScroll = useThrottleFn(() => {
  checkScrollPosition()
}, 100)
</script>

<template>
  <input @input="e => handleSearch(e.target.value)" />
  <div @scroll="handleScroll">...</div>
</template>

7. 打包优化

// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    // 分包策略
    rollupOptions: {
      output: {
        manualChunks: {
          // 第三方库单独打包
          vendor: ['vue', 'vue-router', 'pinia'],
          // UI 库单独打包
          ui: ['element-plus']
        }
      }
    },
    // 启用 gzip 压缩
    reportCompressedSize: true
  }
})

8. 使用 shallowRef/shallowReactive

<script setup>
import { shallowRef, shallowReactive } from 'vue'

// 大对象只需要浅层响应式
const bigData = shallowRef({
  // 大量嵌套数据...
})

// 更新时整体替换
bigData.value = { ...newData }
</script>

关键点

  • v-if/v-show:频繁切换用 v-show,条件稳定用 v-if
  • 路由懒加载:使用动态 import 按需加载页面组件
  • 虚拟滚动:长列表使用虚拟滚动,只渲染可视区域
  • keep-alive:缓存不活跃组件,避免重复渲染
  • 合理使用响应式:大数据用 shallowRef,避免深层响应式开销