Skip to content

⚡ 性能优化

ERP组件库专为高性能而设计,本指南将帮助您充分发挥组件库的性能潜力,构建快速响应的企业级应用。

🎯 性能目标

我们的性能目标:

  • 首屏加载时间 < 2秒
  • 页面切换时间 < 500ms
  • 大数据表格渲染 < 1秒(10万条数据)
  • 内存占用 < 50MB(复杂页面)
  • 包体积 < 500KB(gzipped)

📊 性能监控

内置性能监控

组件库内置了性能监控功能:

typescript
import { PerformanceMonitor } from '@erp/common'

// 启用性能监控
PerformanceMonitor.enable({
  // 监控配置
  trackPageLoad: true,
  trackComponentRender: true,
  trackUserInteraction: true,
  
  // 上报配置
  reportUrl: '/api/performance',
  reportInterval: 30000, // 30秒上报一次
  
  // 阈值配置
  thresholds: {
    pageLoad: 3000,      // 页面加载超过3秒告警
    componentRender: 100, // 组件渲染超过100ms告警
    memoryUsage: 100      // 内存使用超过100MB告警
  }
})

// 手动监控特定操作
const monitor = PerformanceMonitor.start('data-processing')
await processLargeDataSet()
monitor.end() // 自动上报耗时

性能指标收集

typescript
// 收集核心Web指标
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'

getCLS(console.log) // 累积布局偏移
getFID(console.log) // 首次输入延迟
getFCP(console.log) // 首次内容绘制
getLCP(console.log) // 最大内容绘制
getTTFB(console.log) // 首字节时间

// 自定义指标
PerformanceMonitor.measure('table-render-time', () => {
  return renderLargeTable()
})

🚀 组件级优化

1. 虚拟滚动

对于大数据量场景,使用虚拟滚动:

vue
<template>
  <!-- ✅ 推荐:大数据量使用虚拟滚动 -->
  <FkTable 
    :columns="columns"
    :data="largeDataSet"
    virtual
    :item-height="50"
    :buffer-size="10"
    height="400px"
  />
  
  <!-- ✅ 虚拟列表 -->
  <FkVirtualList
    :items="items"
    :item-height="60"
    height="500px"
    v-slot="{ item, index }"
  >
    <UserItem :user="item" :index="index" />
  </FkVirtualList>
</template>

<script setup lang="ts">
// 大数据集处理
const largeDataSet = ref<UserData[]>([])

// 分批加载数据
const loadDataInBatches = async () => {
  const batchSize = 1000
  const totalBatches = Math.ceil(totalCount / batchSize)
  
  for (let i = 0; i < totalBatches; i++) {
    const batch = await loadBatch(i, batchSize)
    largeDataSet.value.push(...batch)
    
    // 让出主线程,避免阻塞UI
    await nextTick()
  }
}
</script>

2. 懒加载和按需渲染

vue
<template>
  <div class="dashboard">
    <!-- ✅ 懒加载组件 -->
    <Suspense>
      <template #default>
        <AsyncChart v-if="showChart" :data="chartData" />
      </template>
      <template #fallback>
        <ChartSkeleton />
      </template>
    </Suspense>
    
    <!-- ✅ 条件渲染 -->
    <ExpensiveComponent v-if="shouldRender" />
    
    <!-- ✅ 延迟渲染 -->
    <LazyComponent v-show="isVisible" />
  </div>
</template>

<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

// 异步组件
const AsyncChart = defineAsyncComponent({
  loader: () => import('./Chart.vue'),
  loadingComponent: ChartSkeleton,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 3000
})

// 懒加载组件
const LazyComponent = defineAsyncComponent(() => 
  import('./LazyComponent.vue')
)

// 可见性检测
const { isVisible } = useIntersectionObserver(target)
</script>

3. 内存优化

typescript
// ✅ 推荐:及时清理资源
export function useResourceCleanup() {
  const timers = new Set<number>()
  const observers = new Set<IntersectionObserver>()
  const subscriptions = new Set<() => void>()
  
  const addTimer = (id: number) => timers.add(id)
  const addObserver = (observer: IntersectionObserver) => observers.add(observer)
  const addSubscription = (unsubscribe: () => void) => subscriptions.add(unsubscribe)
  
  onUnmounted(() => {
    // 清理定时器
    timers.forEach(id => clearTimeout(id))
    timers.clear()
    
    // 清理观察器
    observers.forEach(observer => observer.disconnect())
    observers.clear()
    
    // 清理订阅
    subscriptions.forEach(unsubscribe => unsubscribe())
    subscriptions.clear()
  })
  
  return { addTimer, addObserver, addSubscription }
}

// 使用示例
const { addTimer, addObserver } = useResourceCleanup()

// 添加定时器
const timerId = setTimeout(() => {}, 1000)
addTimer(timerId)

// 添加观察器
const observer = new IntersectionObserver(() => {})
addObserver(observer)

🎨 渲染优化

1. 减少重渲染

vue
<template>
  <!-- ✅ 使用v-memo减少重渲染 -->
  <div v-memo="[user.id, user.status]">
    <UserCard :user="user" />
  </div>
  
  <!-- ✅ 使用key优化列表渲染 -->
  <div 
    v-for="item in items" 
    :key="`${item.id}-${item.version}`"
  >
    <ItemCard :item="item" />
  </div>
</template>

<script setup lang="ts">
// ✅ 使用shallowRef减少深度响应
const largeObject = shallowRef({
  data: new Array(10000).fill(0).map((_, i) => ({ id: i }))
})

// ✅ 使用computed缓存计算结果
const expensiveComputed = computed(() => {
  return heavyCalculation(props.data)
})

// ✅ 使用watchEffect优化副作用
watchEffect(() => {
  if (props.visible) {
    // 只在可见时执行
    updateData()
  }
})
</script>

2. CSS优化

css
/* ✅ 使用transform和opacity进行动画 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: translateY(10px);
}

/* ✅ 使用will-change提示浏览器优化 */
.animated-element {
  will-change: transform, opacity;
}

/* ✅ 使用contain优化重排重绘 */
.isolated-component {
  contain: layout style paint;
}

/* ✅ 使用GPU加速 */
.gpu-accelerated {
  transform: translateZ(0);
  backface-visibility: hidden;
}

📦 打包优化

1. 代码分割

typescript
// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // 框架代码
          framework: ['vue', 'vue-router', 'pinia'],
          
          // ERP组件库
          'erp-common': ['@erp/common'],
          'erp-biz': ['@erp/biz'],
          'erp-icons': ['@erp/icons'],
          
          // 工具库
          utils: ['lodash-es', 'dayjs', 'axios'],
          
          // 图表库
          charts: ['echarts', 'chart.js'],
          
          // 按页面分割
          dashboard: ['./src/views/dashboard'],
          user: ['./src/views/user'],
          order: ['./src/views/order']
        }
      }
    }
  }
})

// 路由懒加载
const routes = [
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue')
  },
  {
    path: '/user',
    component: () => import('./views/User.vue')
  }
]

2. Tree Shaking

typescript
// ✅ 推荐:按需导入
import { FkInput, FkButton } from '@erp/common'
import { debounce } from 'lodash-es'
import { format } from 'date-fns'

// ❌ 不推荐:全量导入
import * as ERP from '@erp/common'
import _ from 'lodash'
import * as dateFns from 'date-fns'

// ✅ 配置Tree Shaking
// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      treeshake: {
        moduleSideEffects: false,
        propertyReadSideEffects: false,
        tryCatchDeoptimization: false
      }
    }
  }
})

3. 压缩优化

typescript
// vite.config.ts
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    // 打包分析
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true
    })
  ],
  build: {
    // 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
        pure_funcs: ['console.log', 'console.info']
      },
      mangle: {
        safari10: true
      }
    },
    
    // 启用gzip
    reportCompressedSize: true,
    
    // 分包大小限制
    chunkSizeWarningLimit: 1000
  }
})

🌐 网络优化

1. 资源预加载

typescript
// 预加载关键资源
const preloadCriticalResources = () => {
  // 预加载字体
  const fontLink = document.createElement('link')
  fontLink.rel = 'preload'
  fontLink.href = '/fonts/HarmonyOS-Sans.woff2'
  fontLink.as = 'font'
  fontLink.type = 'font/woff2'
  fontLink.crossOrigin = 'anonymous'
  document.head.appendChild(fontLink)
  
  // 预加载关键CSS
  const cssLink = document.createElement('link')
  cssLink.rel = 'preload'
  cssLink.href = '/css/critical.css'
  cssLink.as = 'style'
  document.head.appendChild(cssLink)
  
  // 预加载关键组件
  import('./CriticalComponent.vue')
}

// 预连接外部域名
const preconnectDomains = [
  'https://api.example.com',
  'https://cdn.example.com'
]

preconnectDomains.forEach(domain => {
  const link = document.createElement('link')
  link.rel = 'preconnect'
  link.href = domain
  document.head.appendChild(link)
})

2. 缓存策略

typescript
// HTTP缓存配置
const cacheConfig = {
  // 静态资源长期缓存
  assets: {
    'Cache-Control': 'public, max-age=31536000, immutable'
  },
  
  // API数据短期缓存
  api: {
    'Cache-Control': 'public, max-age=300'
  },
  
  // HTML文件不缓存
  html: {
    'Cache-Control': 'no-cache, no-store, must-revalidate'
  }
}

// Service Worker缓存
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
}

// 内存缓存
const cache = new Map()

export function useCache<T>(key: string, fetcher: () => Promise<T>, ttl = 5 * 60 * 1000) {
  const cached = cache.get(key)
  
  if (cached && Date.now() - cached.timestamp < ttl) {
    return Promise.resolve(cached.data)
  }
  
  return fetcher().then(data => {
    cache.set(key, {
      data,
      timestamp: Date.now()
    })
    return data
  })
}

📱 移动端优化

1. 触摸优化

css
/* 优化触摸响应 */
.touch-element {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

/* 优化滚动性能 */
.scroll-container {
  -webkit-overflow-scrolling: touch;
  overflow-scrolling: touch;
}

/* 减少重绘 */
.optimized-element {
  transform: translateZ(0);
  will-change: transform;
}

2. 响应式图片

vue
<template>
  <!-- ✅ 响应式图片 -->
  <picture>
    <source 
      media="(max-width: 768px)" 
      srcset="/images/mobile.webp"
      type="image/webp"
    >
    <source 
      media="(max-width: 768px)" 
      srcset="/images/mobile.jpg"
    >
    <source 
      srcset="/images/desktop.webp"
      type="image/webp"
    >
    <img 
      src="/images/desktop.jpg" 
      alt="描述"
      loading="lazy"
      decoding="async"
    >
  </picture>
  
  <!-- ✅ 懒加载图片 -->
  <FkImage
    :src="imageSrc"
    lazy
    :placeholder="placeholderSrc"
    @load="handleImageLoad"
  />
</template>

🔧 开发工具

1. 性能分析工具

typescript
// 性能分析装饰器
export function performanceTrack(name: string) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value
    
    descriptor.value = async function (...args: any[]) {
      const start = performance.now()
      
      try {
        const result = await originalMethod.apply(this, args)
        const end = performance.now()
        
        console.log(`[Performance] ${name}: ${end - start}ms`)
        
        // 上报性能数据
        PerformanceMonitor.report(name, end - start)
        
        return result
      } catch (error) {
        const end = performance.now()
        console.error(`[Performance] ${name} failed after ${end - start}ms:`, error)
        throw error
      }
    }
    
    return descriptor
  }
}

// 使用示例
class UserService {
  @performanceTrack('loadUsers')
  async loadUsers() {
    return await api.get('/users')
  }
}

2. 内存泄漏检测

typescript
// 内存泄漏检测工具
export class MemoryLeakDetector {
  private static instances = new WeakMap()
  private static timers = new Set<number>()
  private static listeners = new Map<EventTarget, Map<string, EventListener>>()
  
  static trackComponent(component: any, name: string) {
    this.instances.set(component, {
      name,
      createdAt: Date.now(),
      stack: new Error().stack
    })
  }
  
  static trackTimer(id: number) {
    this.timers.add(id)
    return id
  }
  
  static clearTimer(id: number) {
    clearTimeout(id)
    this.timers.delete(id)
  }
  
  static trackListener(target: EventTarget, event: string, listener: EventListener) {
    if (!this.listeners.has(target)) {
      this.listeners.set(target, new Map())
    }
    this.listeners.get(target)!.set(event, listener)
  }
  
  static removeListener(target: EventTarget, event: string) {
    const listeners = this.listeners.get(target)
    if (listeners) {
      const listener = listeners.get(event)
      if (listener) {
        target.removeEventListener(event, listener)
        listeners.delete(event)
      }
    }
  }
  
  static checkLeaks() {
    console.log('Active timers:', this.timers.size)
    console.log('Active listeners:', this.listeners.size)
    
    // 检查未清理的定时器
    if (this.timers.size > 0) {
      console.warn('Potential timer leaks:', Array.from(this.timers))
    }
    
    // 检查未清理的事件监听器
    if (this.listeners.size > 0) {
      console.warn('Potential listener leaks:', this.listeners)
    }
  }
}

// 在开发环境中使用
if (process.env.NODE_ENV === 'development') {
  setInterval(() => {
    MemoryLeakDetector.checkLeaks()
  }, 10000)
}

📊 性能监控面板

vue
<template>
  <div v-if="showPerformancePanel" class="performance-panel">
    <div class="panel-header">
      <h3>性能监控</h3>
      <button @click="togglePanel">关闭</button>
    </div>
    
    <div class="metrics">
      <div class="metric">
        <label>FPS</label>
        <span :class="{ warning: fps < 30 }">{{ fps }}</span>
      </div>
      
      <div class="metric">
        <label>内存使用</label>
        <span :class="{ warning: memoryUsage > 50 }">{{ memoryUsage }}MB</span>
      </div>
      
      <div class="metric">
        <label>组件数量</label>
        <span>{{ componentCount }}</span>
      </div>
      
      <div class="metric">
        <label>渲染时间</label>
        <span :class="{ warning: renderTime > 16 }">{{ renderTime }}ms</span>
      </div>
    </div>
    
    <div class="charts">
      <canvas ref="fpsChart" width="200" height="100"></canvas>
      <canvas ref="memoryChart" width="200" height="100"></canvas>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'

const showPerformancePanel = ref(false)
const fps = ref(60)
const memoryUsage = ref(0)
const componentCount = ref(0)
const renderTime = ref(0)

let animationId: number
let lastTime = 0
let frameCount = 0

const updateMetrics = () => {
  // 计算FPS
  const now = performance.now()
  frameCount++
  
  if (now - lastTime >= 1000) {
    fps.value = Math.round((frameCount * 1000) / (now - lastTime))
    frameCount = 0
    lastTime = now
  }
  
  // 获取内存使用情况
  if ('memory' in performance) {
    const memory = (performance as any).memory
    memoryUsage.value = Math.round(memory.usedJSHeapSize / 1024 / 1024)
  }
  
  // 获取组件数量
  componentCount.value = document.querySelectorAll('[data-v-]').length
  
  animationId = requestAnimationFrame(updateMetrics)
}

onMounted(() => {
  // 快捷键显示性能面板
  const handleKeydown = (e: KeyboardEvent) => {
    if (e.ctrlKey && e.shiftKey && e.key === 'P') {
      showPerformancePanel.value = !showPerformancePanel.value
    }
  }
  
  document.addEventListener('keydown', handleKeydown)
  updateMetrics()
  
  onUnmounted(() => {
    document.removeEventListener('keydown', handleKeydown)
    cancelAnimationFrame(animationId)
  })
})

const togglePanel = () => {
  showPerformancePanel.value = false
}
</script>

<style scoped>
.performance-panel {
  position: fixed;
  top: 20px;
  right: 20px;
  width: 300px;
  background: rgba(0, 0, 0, 0.9);
  color: white;
  padding: 16px;
  border-radius: 8px;
  font-family: monospace;
  z-index: 10000;
  backdrop-filter: blur(10px);
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.metrics {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-bottom: 16px;
}

.metric {
  display: flex;
  justify-content: space-between;
  padding: 8px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 4px;
}

.metric span.warning {
  color: #ff6b6b;
}

.charts {
  display: flex;
  gap: 8px;
}

.charts canvas {
  background: rgba(255, 255, 255, 0.1);
  border-radius: 4px;
}
</style>

🎯 性能优化清单

开发阶段

  • [ ] 使用TypeScript进行类型检查
  • [ ] 配置ESLint性能规则
  • [ ] 使用Vue DevTools分析组件性能
  • [ ] 启用Vite的HMR和预构建
  • [ ] 使用性能监控装饰器

构建阶段

  • [ ] 启用Tree Shaking
  • [ ] 配置代码分割策略
  • [ ] 压缩JavaScript和CSS
  • [ ] 优化图片资源
  • [ ] 生成Source Map用于调试

部署阶段

  • [ ] 配置CDN加速
  • [ ] 启用Gzip/Brotli压缩
  • [ ] 设置合理的缓存策略
  • [ ] 配置HTTP/2推送
  • [ ] 监控Core Web Vitals

运行时优化

  • [ ] 使用虚拟滚动处理大数据
  • [ ] 实现组件懒加载
  • [ ] 优化图片加载策略
  • [ ] 减少不必要的重渲染
  • [ ] 及时清理资源和事件监听器

通过遵循这些性能优化实践,您可以构建出快速、流畅的ERP应用。记住,性能优化是一个持续的过程,需要在开发的各个阶段都保持关注。

如需更多帮助,请查看:

基于 MIT 许可发布