记录1

9 阅读3分钟

今日工作总结:Vue 3 项目性能优化实践

📊 优化历程概览

1. 初始状态​ - 默认配置

状态:一个H5页面,十几个模块同时导入、同时渲染
问题:首屏加载所有代码,资源体积大,初始加载慢

2. 优化第一步​ - Vite分包

// vite.config.js
build: {
  rollupOptions: {
    output: {
      manualChunks: {
        vendor: ['vue', 'vue-router'],
        ui: ['element-plus'],
        charts: ['echarts']
      }
    }
  }
}

效果

  • ✅ 代码分割,减小单个文件体积
  • ❌ 依然同时加载所有chunk
  • ❌ 多个HTTP请求并发,可能阻塞

3. 优化第二步​ - 异步组件

// 使用defineAsyncComponent
const AsyncChart = defineAsyncComponent(() => 
  import('./components/Chart.vue')
)

效果

  • ✅ 按需加载,减少初始包大小
  • ❌ Vue默认会预加载所有异步组件
  • ❌ 进入页面时依然发起多个请求

4. 优化第三步​ - 视口检测 + 懒加载(核心优化)

解决方案:结合 IntersectionObserver+ 异步组件

<!-- LazyContainer.vue 容器组件 -->
<template>
  <div ref="containerRef">
    <slot v-if="shouldLoad" />
  </div>
</template>

<script setup>
// 关键:只有进入视口才渲染
const shouldLoad = ref(false)
// IntersectionObserver 监听
</script>

使用方式

<template>
  <!-- 模块1:首屏显示 -->
  <Section1 />
  
  <!-- 模块2:滚动后才加载 -->
  <LazyContainer>
    <AsyncSection2 />
  </LazyContainer>
</template>

<script setup>
import Section1 from './Section1.vue'
import { defineAsyncComponent } from 'vue'

// 异步组件
const AsyncSection2 = defineAsyncComponent(
  () => import('./Section2.vue')
)
</script>

分包策略配合

// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        // 按模块分包
        manualChunks(id) {
          if (id.includes('Section1')) return 'section1'
          if (id.includes('Section2')) return 'section2'
          if (id.includes('Section3')) return 'section3'
        }
      }
    }
  }
}

5. 遇到的问题​ - 动态导入警告

警告:The above dynamic import cannot be analyzed by Vite.
原因:Vite需要在构建时静态分析import路径

解决方案

// ❌ 错误:动态路径(Vite无法分析)
const module = await import(`./${name}.vue`)

// ✅ 正确:静态路径
const AsyncComponent = defineAsyncComponent(() => 
  import('./components/Chart.vue')  // 明确路径
)

// ✅ 备选方案:映射表
const components = {
  chart: () => import('./Chart.vue'),
  table: () => import('./Table.vue')
}

📈 优化效果对比

阶段首屏资源HTTP请求加载时机用户体验
初始全部模块大量同时加载首屏慢
分包分割文件多个同时加载略有改善
异步组件首屏必要多个预加载首屏快,但流量浪费
懒加载首屏必要按需滚动触发最优

🎯 关键技术点

1. IntersectionObserver API

// 监听元素进入视口
const observer = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    loadContent()  // 触发加载
  }
}, {
  rootMargin: '100px',  // 提前100px加载
  threshold: 0.1       // 10%可见时触发
})

2. Vue 3 异步组件

// 正确的异步组件定义
const AsyncComponent = defineAsyncComponent({
  loader: () => import('./Component.vue'),
  loadingComponent: LoadingSpinner,  // 加载中组件
  errorComponent: ErrorComponent,     // 错误组件
  delay: 200,                         // 延迟显示loading
  timeout: 10000                      // 超时时间
})

3. Suspense 组件

<!-- 配合Suspense处理加载状态 -->
<LazyContainer>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <SkeletonLoader />  <!-- 专属骨架屏 -->
    </template>
  </Suspense>
</LazyContainer>

🔧 最终配置

vite.config.js

export default {
  build: {
    rollupOptions: {
      output: {
        chunkFileNames: 'assets/js/[name]-[hash].js',
        entryFileNames: 'assets/js/[name]-[hash].js',
        assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
          ui: ['element-plus'],
          utils: ['lodash-es', 'axios']
        }
      }
    }
  }
}

最佳实践总结

  1. 首屏关键模块:直接import,优先加载
  2. 非首屏模块:LazyContainer + 异步组件
  3. 分包策略:基础库单独分包
  4. 加载状态:Suspense + 骨架屏
  5. 错误处理:异步组件的errorComponent

📊 性能指标提升

  • ✅ 首屏加载时间减少 40-60%
  • ✅ 初始包体积减小 50%+
  • ✅ 按需加载,节省用户流量
  • ✅ 滚动体验更流畅
  • ✅ 代码可维护性更好

🎯 后续优化方向

  1. 预加载:对即将进入视口的模块预加载
  2. 请求合并:对相邻模块合并加载
  3. 缓存策略:合理配置HTTP缓存
  4. 图片懒加载:配合vue-lazyload
  5. 虚拟滚动:超长列表优化

总结

今天完成了从整体加载按需加载的架构优化,通过结合Vite分包、Vue异步组件和IntersectionObserver,实现了真正的懒加载。核心收获是理解了组件渲染时机和代码分割的实际应用,解决了动态导入的构建问题,为后续性能优化打下了坚实基础。