Skip to content

性能优化

全面的 Pinia store 性能优化指南,包括内存管理、懒加载、缓存策略和大型应用的高级优化技术。

功能特性

  • 🚀 Store 懒加载和代码分割
  • 💾 内存管理和清理
  • 🔄 智能缓存策略
  • 📊 性能监控和分析
  • 🎯 选择性响应式和计算属性优化
  • 🔀 状态规范化和反规范化
  • 📦 打包大小优化
  • ⚡ 大数据集虚拟滚动
  • 🧩 Store 组合优化
  • 🔧 开发环境与生产环境优化

核心性能类型

typescript
// types/performance.ts
export interface PerformanceMetrics {
  storeCreationTime: number
  actionExecutionTime: Record<string, number[]>
  memoryUsage: {
    stores: Record<string, number>
    total: number
  }
  subscriptionCount: Record<string, number>
  computedCount: Record<string, number>
}

export interface CacheConfig {
  ttl?: number // 生存时间(毫秒)
  maxSize?: number
  strategy?: 'lru' | 'fifo' | 'lfu'
  serialize?: boolean
}

export interface LazyLoadConfig {
  preload?: boolean
  timeout?: number
  retries?: number
  fallback?: any
}

export interface OptimizationConfig {
  enableLazyLoading?: boolean
  enableCaching?: boolean
  enableMemoryCleanup?: boolean
  enablePerformanceMonitoring?: boolean
  maxStoreInstances?: number
  cleanupInterval?: number
}

export interface VirtualScrollConfig {
  itemHeight: number
  bufferSize?: number
  overscan?: number
  estimatedItemHeight?: number
}

性能监控器

typescript
// utils/performance-monitor.ts
import type { PerformanceMetrics } from '@/types'

class PerformanceMonitor {
  private metrics: PerformanceMetrics = {
    storeCreationTime: 0,
    actionExecutionTime: {},
    memoryUsage: {
      stores: {},
      total: 0
    },
    subscriptionCount: {},
    computedCount: {}
  }
  
  private observers = new Map<string, PerformanceObserver>()
  private isMonitoring = false
  
  startMonitoring() {
    if (this.isMonitoring) return
    
    this.isMonitoring = true
    
    // 监控内存使用
    if ('memory' in performance) {
      this.startMemoryMonitoring()
    }
    
    // 监控长任务
    if ('PerformanceObserver' in window) {
      this.startLongTaskMonitoring()
    }
    
    // 监控 store 操作
    this.startStoreMonitoring()
  }
  
  stopMonitoring() {
    this.isMonitoring = false
    
    this.observers.forEach(observer => {
      observer.disconnect()
    })
    this.observers.clear()
  }
  
  private startMemoryMonitoring() {
    const updateMemoryUsage = () => {
      if ('memory' in performance) {
        const memory = (performance as any).memory
        this.metrics.memoryUsage.total = memory.usedJSHeapSize
      }
    }
    
    updateMemoryUsage()
    setInterval(updateMemoryUsage, 5000) // 每 5 秒更新一次
  }
  
  private startLongTaskMonitoring() {
    const observer = new PerformanceObserver((list) => {
      const entries = list.getEntries()
      entries.forEach(entry => {
        if (entry.duration > 50) { // 超过 50ms 的任务
          console.warn(`检测到长任务: ${entry.duration}ms`, entry)
        }
      })
    })
    
    observer.observe({ entryTypes: ['longtask'] })
    this.observers.set('longtask', observer)
  }
  
  private startStoreMonitoring() {
    // 这将由性能插件调用
  }
  
  recordStoreCreation(storeId: string, duration: number) {
    this.metrics.storeCreationTime += duration
    console.log(`Store '${storeId}' 创建耗时 ${duration}ms`)
  }
  
  recordActionExecution(storeId: string, actionName: string, duration: number) {
    const key = `${storeId}.${actionName}`
    if (!this.metrics.actionExecutionTime[key]) {
      this.metrics.actionExecutionTime[key] = []
    }
    this.metrics.actionExecutionTime[key].push(duration)
    
    // 只保留最近 100 次测量
    if (this.metrics.actionExecutionTime[key].length > 100) {
      this.metrics.actionExecutionTime[key] = this.metrics.actionExecutionTime[key].slice(-100)
    }
  }
  
  recordMemoryUsage(storeId: string, size: number) {
    this.metrics.memoryUsage.stores[storeId] = size
  }
  
  recordSubscriptionCount(storeId: string, count: number) {
    this.metrics.subscriptionCount[storeId] = count
  }
  
  recordComputedCount(storeId: string, count: number) {
    this.metrics.computedCount[storeId] = count
  }
  
  getMetrics(): PerformanceMetrics {
    return { ...this.metrics }
  }
  
  getActionStats(storeId: string, actionName: string) {
    const key = `${storeId}.${actionName}`
    const times = this.metrics.actionExecutionTime[key] || []
    
    if (times.length === 0) {
      return null
    }
    
    const sorted = [...times].sort((a, b) => a - b)
    const avg = times.reduce((sum, time) => sum + time, 0) / times.length
    const median = sorted[Math.floor(sorted.length / 2)]
    const p95 = sorted[Math.floor(sorted.length * 0.95)]
    
    return {
      count: times.length,
      average: avg,
      median,
      p95,
      min: sorted[0],
      max: sorted[sorted.length - 1]
    }
  }
  
  generateReport(): string {
    const metrics = this.getMetrics()
    
    let report = '=== Pinia 性能报告 ===\n\n'
    
    // Store 创建时间
    report += `总 Store 创建时间: ${metrics.storeCreationTime}ms\n\n`
    
    // 内存使用
    report += '内存使用:\n'
    report += `  总计: ${(metrics.memoryUsage.total / 1024 / 1024).toFixed(2)} MB\n`
    Object.entries(metrics.memoryUsage.stores).forEach(([store, size]) => {
      report += `  ${store}: ${(size / 1024).toFixed(2)} KB\n`
    })
    report += '\n'
    
    // Action 性能(前 10 个最慢的)
    report += 'Action 性能(前 10 个最慢的):\n'
    const actionStats = Object.entries(metrics.actionExecutionTime)
      .map(([action, times]) => {
        const avg = times.reduce((sum, time) => sum + time, 0) / times.length
        return { action, average: avg, count: times.length }
      })
      .sort((a, b) => b.average - a.average)
      .slice(0, 10)
    
    actionStats.forEach(({ action, average, count }) => {
      report += `  ${action}: ${average.toFixed(2)}ms (${count} 次调用)\n`
    })
    
    return report
  }
}

export const performanceMonitor = new PerformanceMonitor()

缓存系统

typescript
// utils/cache.ts
import type { CacheConfig } from '@/types'

interface CacheEntry<T> {
  value: T
  timestamp: number
  accessCount: number
  lastAccessed: number
}

class Cache<T = any> {
  private cache = new Map<string, CacheEntry<T>>()
  private config: Required<CacheConfig>
  private cleanupTimer: number | null = null
  
  constructor(config: CacheConfig = {}) {
    this.config = {
      ttl: 5 * 60 * 1000, // 5 分钟
      maxSize: 100,
      strategy: 'lru',
      serialize: false,
      ...config
    }
    
    this.startCleanup()
  }
  
  set(key: string, value: T): void {
    // 如果缓存已满,移除过期条目
    if (this.cache.size >= this.config.maxSize) {
      this.evict()
    }
    
    const entry: CacheEntry<T> = {
      value: this.config.serialize ? JSON.parse(JSON.stringify(value)) : value,
      timestamp: Date.now(),
      accessCount: 0,
      lastAccessed: Date.now()
    }
    
    this.cache.set(key, entry)
  }
  
  get(key: string): T | undefined {
    const entry = this.cache.get(key)
    
    if (!entry) {
      return undefined
    }
    
    // 检查是否过期
    if (this.isExpired(entry)) {
      this.cache.delete(key)
      return undefined
    }
    
    // 更新访问统计
    entry.accessCount++
    entry.lastAccessed = Date.now()
    
    return entry.value
  }
  
  has(key: string): boolean {
    const entry = this.cache.get(key)
    return entry ? !this.isExpired(entry) : false
  }
  
  delete(key: string): boolean {
    return this.cache.delete(key)
  }
  
  clear(): void {
    this.cache.clear()
  }
  
  private isExpired(entry: CacheEntry<T>): boolean {
    return Date.now() - entry.timestamp > this.config.ttl
  }
  
  private evict(): void {
    if (this.cache.size === 0) return
    
    let keyToRemove: string
    
    switch (this.config.strategy) {
      case 'lru': // 最近最少使用
        keyToRemove = this.findLRU()
        break
      case 'lfu': // 最少使用频率
        keyToRemove = this.findLFU()
        break
      case 'fifo': // 先进先出
      default:
        keyToRemove = this.cache.keys().next().value
        break
    }
    
    this.cache.delete(keyToRemove)
  }
  
  private findLRU(): string {
    let oldestKey = ''
    let oldestTime = Date.now()
    
    for (const [key, entry] of this.cache) {
      if (entry.lastAccessed < oldestTime) {
        oldestTime = entry.lastAccessed
        oldestKey = key
      }
    }
    
    return oldestKey
  }
  
  private findLFU(): string {
    let leastUsedKey = ''
    let leastCount = Infinity
    
    for (const [key, entry] of this.cache) {
      if (entry.accessCount < leastCount) {
        leastCount = entry.accessCount
        leastUsedKey = key
      }
    }
    
    return leastUsedKey
  }
  
  private startCleanup(): void {
    this.cleanupTimer = setInterval(() => {
      this.cleanup()
    }, this.config.ttl / 2) as any
  }
  
  private cleanup(): void {
    const now = Date.now()
    const keysToDelete: string[] = []
    
    for (const [key, entry] of this.cache) {
      if (now - entry.timestamp > this.config.ttl) {
        keysToDelete.push(key)
      }
    }
    
    keysToDelete.forEach(key => this.cache.delete(key))
  }
  
  getStats() {
    return {
      size: this.cache.size,
      maxSize: this.config.maxSize,
      hitRate: this.calculateHitRate(),
      oldestEntry: this.getOldestEntry(),
      mostAccessed: this.getMostAccessed()
    }
  }
  
  private calculateHitRate(): number {
    // 在实际实现中需要单独跟踪
    return 0
  }
  
  private getOldestEntry(): { key: string; age: number } | null {
    let oldest: { key: string; age: number } | null = null
    const now = Date.now()
    
    for (const [key, entry] of this.cache) {
      const age = now - entry.timestamp
      if (!oldest || age > oldest.age) {
        oldest = { key, age }
      }
    }
    
    return oldest
  }
  
  private getMostAccessed(): { key: string; count: number } | null {
    let mostAccessed: { key: string; count: number } | null = null
    
    for (const [key, entry] of this.cache) {
      if (!mostAccessed || entry.accessCount > mostAccessed.count) {
        mostAccessed = { key, count: entry.accessCount }
      }
    }
    
    return mostAccessed
  }
  
  destroy(): void {
    if (this.cleanupTimer) {
      clearInterval(this.cleanupTimer)
      this.cleanupTimer = null
    }
    this.clear()
  }
}

export { Cache }

懒加载 Store 工厂

typescript
// utils/lazy-store.ts
import { defineStore } from 'pinia'
import type { LazyLoadConfig } from '@/types'

interface LazyStoreDefinition {
  id: string
  loader: () => Promise<any>
  config?: LazyLoadConfig
}

class LazyStoreManager {
  private loadingStores = new Map<string, Promise<any>>()
  private loadedStores = new Map<string, any>()
  private failedStores = new Set<string>()
  
  createLazyStore<T>(definition: LazyStoreDefinition) {
    const { id, loader, config = {} } = definition
    const { preload = false, timeout = 10000, retries = 3, fallback } = config
    
    // 创建占位符 store
    const lazyStore = defineStore(id, () => {
      const isLoading = ref(false)
      const isLoaded = ref(false)
      const error = ref<Error | null>(null)
      const store = ref<T | null>(null)
      
      const load = async (): Promise<T> => {
        if (this.loadedStores.has(id)) {
          return this.loadedStores.get(id)
        }
        
        if (this.loadingStores.has(id)) {
          return this.loadingStores.get(id)
        }
        
        isLoading.value = true
        error.value = null
        
        const loadPromise = this.loadWithRetry(loader, retries, timeout)
        this.loadingStores.set(id, loadPromise)
        
        try {
          const loadedStore = await loadPromise
          this.loadedStores.set(id, loadedStore)
          this.loadingStores.delete(id)
          this.failedStores.delete(id)
          
          store.value = loadedStore
          isLoaded.value = true
          
          return loadedStore
        } catch (err) {
          this.loadingStores.delete(id)
          this.failedStores.add(id)
          error.value = err as Error
          
          if (fallback) {
            store.value = fallback
            return fallback
          }
          
          throw err
        } finally {
          isLoading.value = false
        }
      }
      
      const reload = async (): Promise<T> => {
        this.loadedStores.delete(id)
        this.loadingStores.delete(id)
        this.failedStores.delete(id)
        isLoaded.value = false
        return load()
      }
      
      const unload = () => {
        this.loadedStores.delete(id)
        this.loadingStores.delete(id)
        store.value = null
        isLoaded.value = false
        error.value = null
      }
      
      // 如果启用预加载,自动加载
      if (preload) {
        load().catch(() => {}) // 忽略预加载错误
      }
      
      return {
        isLoading: readonly(isLoading),
        isLoaded: readonly(isLoaded),
        error: readonly(error),
        store: readonly(store),
        load,
        reload,
        unload
      }
    })
    
    return lazyStore
  }
  
  private async loadWithRetry(
    loader: () => Promise<any>,
    retries: number,
    timeout: number
  ): Promise<any> {
    let lastError: Error
    
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await Promise.race([
          loader(),
          new Promise((_, reject) => 
            setTimeout(() => reject(new Error('加载超时')), timeout)
          )
        ])
      } catch (error) {
        lastError = error as Error
        
        if (attempt < retries) {
          // 指数退避
          const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000)
          await new Promise(resolve => setTimeout(resolve, delay))
        }
      }
    }
    
    throw lastError!
  }
  
  preloadStore(id: string): Promise<any> {
    const store = this.loadedStores.get(id)
    if (store) {
      return Promise.resolve(store)
    }
    
    const loading = this.loadingStores.get(id)
    if (loading) {
      return loading
    }
    
    throw new Error(`Store '${id}' 未找到或未配置懒加载`)
  }
  
  getLoadedStores(): string[] {
    return Array.from(this.loadedStores.keys())
  }
  
  getFailedStores(): string[] {
    return Array.from(this.failedStores)
  }
  
  clearCache(): void {
    this.loadedStores.clear()
    this.loadingStores.clear()
    this.failedStores.clear()
  }
}

export const lazyStoreManager = new LazyStoreManager()

// 创建懒加载 store 的辅助函数
export function createLazyStore<T>(definition: LazyStoreDefinition) {
  return lazyStoreManager.createLazyStore<T>(definition)
}

虚拟滚动 Store

typescript
// stores/virtual-scroll.ts
import { defineStore } from 'pinia'
import type { VirtualScrollConfig } from '@/types'

export const useVirtualScrollStore = defineStore('virtual-scroll', () => {
  // 配置
  const config = ref<VirtualScrollConfig>({
    itemHeight: 50,
    bufferSize: 5,
    overscan: 3,
    estimatedItemHeight: 50
  })
  
  // 状态
  const items = ref<any[]>([])
  const totalItems = ref(0)
  const scrollTop = ref(0)
  const containerHeight = ref(0)
  const isLoading = ref(false)
  const loadedRanges = ref<Array<{ start: number; end: number }>>([])
  
  // 计算属性
  const itemHeight = computed(() => config.value.itemHeight)
  const bufferSize = computed(() => config.value.bufferSize || 5)
  const overscan = computed(() => config.value.overscan || 3)
  
  const visibleRange = computed(() => {
    const start = Math.floor(scrollTop.value / itemHeight.value)
    const end = Math.min(
      start + Math.ceil(containerHeight.value / itemHeight.value),
      totalItems.value
    )
    
    return {
      start: Math.max(0, start - overscan.value),
      end: Math.min(totalItems.value, end + overscan.value)
    }
  })
  
  const visibleItems = computed(() => {
    const { start, end } = visibleRange.value
    return items.value.slice(start, end).map((item, index) => ({
      ...item,
      index: start + index,
      top: (start + index) * itemHeight.value
    }))
  })
  
  const totalHeight = computed(() => totalItems.value * itemHeight.value)
  
  const offsetY = computed(() => visibleRange.value.start * itemHeight.value)
  
  // Actions
  function updateConfig(newConfig: Partial<VirtualScrollConfig>) {
    config.value = { ...config.value, ...newConfig }
  }
  
  function setScrollTop(value: number) {
    scrollTop.value = value
    loadVisibleItems()
  }
  
  function setContainerHeight(height: number) {
    containerHeight.value = height
    loadVisibleItems()
  }
  
  function setTotalItems(count: number) {
    totalItems.value = count
    
    // 调整 items 数组大小
    if (items.value.length > count) {
      items.value = items.value.slice(0, count)
    } else if (items.value.length < count) {
      const newItems = new Array(count - items.value.length).fill(null)
      items.value.push(...newItems)
    }
  }
  
  async function loadVisibleItems() {
    const { start, end } = visibleRange.value
    
    // 检查范围是否已加载
    const isRangeLoaded = loadedRanges.value.some(
      range => range.start <= start && range.end >= end
    )
    
    if (isRangeLoaded || isLoading.value) {
      return
    }
    
    // 使用缓冲区扩展范围
    const bufferedStart = Math.max(0, start - bufferSize.value)
    const bufferedEnd = Math.min(totalItems.value, end + bufferSize.value)
    
    isLoading.value = true
    
    try {
      const loadedItems = await loadItemsRange(bufferedStart, bufferedEnd)
      
      // 更新 items 数组
      for (let i = 0; i < loadedItems.length; i++) {
        const index = bufferedStart + i
        if (index < items.value.length) {
          items.value[index] = loadedItems[i]
        }
      }
      
      // 更新已加载范围
      loadedRanges.value.push({
        start: bufferedStart,
        end: bufferedEnd
      })
      
      // 合并重叠范围
      mergeLoadedRanges()
      
    } catch (error) {
      console.error('加载项目失败:', error)
    } finally {
      isLoading.value = false
    }
  }
  
  async function loadItemsRange(start: number, end: number): Promise<any[]> {
    // 这应该由使用者实现
    // 为了演示,我们模拟一个 API 调用
    await new Promise(resolve => setTimeout(resolve, 100))
    
    return Array.from({ length: end - start }, (_, i) => ({
      id: start + i,
      text: `项目 ${start + i}`,
      data: `项目 ${start + i} 的数据`
    }))
  }
  
  function mergeLoadedRanges() {
    loadedRanges.value.sort((a, b) => a.start - b.start)
    
    const merged: Array<{ start: number; end: number }> = []
    
    for (const range of loadedRanges.value) {
      if (merged.length === 0 || merged[merged.length - 1].end < range.start) {
        merged.push(range)
      } else {
        merged[merged.length - 1].end = Math.max(
          merged[merged.length - 1].end,
          range.end
        )
      }
    }
    
    loadedRanges.value = merged
  }
  
  function scrollToIndex(index: number) {
    const targetScrollTop = index * itemHeight.value
    setScrollTop(targetScrollTop)
  }
  
  function scrollToTop() {
    setScrollTop(0)
  }
  
  function scrollToBottom() {
    const maxScrollTop = Math.max(0, totalHeight.value - containerHeight.value)
    setScrollTop(maxScrollTop)
  }
  
  function clearCache() {
    items.value = new Array(totalItems.value).fill(null)
    loadedRanges.value = []
  }
  
  function getItemAt(index: number) {
    return items.value[index]
  }
  
  function updateItem(index: number, item: any) {
    if (index >= 0 && index < items.value.length) {
      items.value[index] = item
    }
  }
  
  // 性能指标
  const metrics = computed(() => {
    const loadedItemsCount = items.value.filter(item => item !== null).length
    const loadedPercentage = totalItems.value > 0 
      ? (loadedItemsCount / totalItems.value) * 100 
      : 0
    
    return {
      totalItems: totalItems.value,
      loadedItems: loadedItemsCount,
      loadedPercentage,
      visibleItems: visibleItems.value.length,
      loadedRanges: loadedRanges.value.length,
      memoryUsage: estimateMemoryUsage()
    }
  })
  
  function estimateMemoryUsage(): number {
    // 粗略估算内存使用量(字节)
    const itemSize = 100 // 每个项目的估计字节数
    return items.value.filter(item => item !== null).length * itemSize
  }
  
  return {
    // 状态
    config: readonly(config),
    items: readonly(items),
    totalItems: readonly(totalItems),
    scrollTop: readonly(scrollTop),
    containerHeight: readonly(containerHeight),
    isLoading: readonly(isLoading),
    
    // 计算属性
    visibleRange,
    visibleItems,
    totalHeight,
    offsetY,
    metrics,
    
    // Actions
    updateConfig,
    setScrollTop,
    setContainerHeight,
    setTotalItems,
    loadVisibleItems,
    scrollToIndex,
    scrollToTop,
    scrollToBottom,
    clearCache,
    getItemAt,
    updateItem
  }
})

优化的 Store 示例

typescript
// stores/optimized-products.ts
import { defineStore } from 'pinia'
import { Cache } from '@/utils/cache'
import { performanceMonitor } from '@/utils/performance-monitor'

interface Product {
  id: string
  name: string
  price: number
  category: string
  description: string
  images: string[]
}

interface ProductsState {
  products: Map<string, Product>
  categories: string[]
  filters: {
    category: string
    priceRange: [number, number]
    search: string
  }
  pagination: {
    page: number
    limit: number
    total: number
  }
  loading: boolean
  error: string | null
}

export const useOptimizedProductsStore = defineStore('optimized-products', () => {
  // 使用 Map 进行 O(1) 查找而不是数组
  const products = ref(new Map<string, Product>())
  const categories = ref<string[]>([])
  const filters = ref({
    category: '',
    priceRange: [0, 1000] as [number, number],
    search: ''
  })
  const pagination = ref({
    page: 1,
    limit: 20,
    total: 0
  })
  const loading = ref(false)
  const error = ref<string | null>(null)
  
  // 昂贵计算的缓存
  const cache = new Cache<any>({
    ttl: 5 * 60 * 1000, // 5 分钟
    maxSize: 50,
    strategy: 'lru'
  })
  
  // 记忆化计算属性
  const filteredProducts = computed(() => {
    const cacheKey = `filtered-${JSON.stringify(filters.value)}`
    
    if (cache.has(cacheKey)) {
      return cache.get(cacheKey)!
    }
    
    const startTime = performance.now()
    
    const filtered = Array.from(products.value.values()).filter(product => {
      // 分类过滤
      if (filters.value.category && product.category !== filters.value.category) {
        return false
      }
      
      // 价格范围过滤
      const [minPrice, maxPrice] = filters.value.priceRange
      if (product.price < minPrice || product.price > maxPrice) {
        return false
      }
      
      // 搜索过滤
      if (filters.value.search) {
        const searchLower = filters.value.search.toLowerCase()
        return product.name.toLowerCase().includes(searchLower) ||
               product.description.toLowerCase().includes(searchLower)
      }
      
      return true
    })
    
    const duration = performance.now() - startTime
    performanceMonitor.recordActionExecution('optimized-products', 'filter', duration)
    
    cache.set(cacheKey, filtered)
    return filtered
  })
  
  const paginatedProducts = computed(() => {
    const start = (pagination.value.page - 1) * pagination.value.limit
    const end = start + pagination.value.limit
    return filteredProducts.value.slice(start, end)
  })
  
  const totalPages = computed(() => {
    return Math.ceil(filteredProducts.value.length / pagination.value.limit)
  })
  
  // 优化的 actions
  async function fetchProducts(options: {
    page?: number
    limit?: number
    category?: string
    force?: boolean
  } = {}) {
    const { page = 1, limit = 20, category, force = false } = options
    
    const cacheKey = `products-${page}-${limit}-${category || 'all'}`
    
    if (!force && cache.has(cacheKey)) {
      const cached = cache.get(cacheKey)!
      updateProductsFromCache(cached)
      return
    }
    
    loading.value = true
    error.value = null
    
    const startTime = performance.now()
    
    try {
      const params = new URLSearchParams({
        page: page.toString(),
        limit: limit.toString(),
        ...(category && { category })
      })
      
      const response = await fetch(`/api/products?${params}`)
      const data = await response.json()
      
      // 批量更新产品
      batchUpdateProducts(data.products)
      
      pagination.value = {
        page: data.page,
        limit: data.limit,
        total: data.total
      }
      
      // 缓存结果
      cache.set(cacheKey, {
        products: data.products,
        pagination: pagination.value
      })
      
    } catch (err) {
      error.value = (err as Error).message
    } finally {
      loading.value = false
      
      const duration = performance.now() - startTime
      performanceMonitor.recordActionExecution('optimized-products', 'fetchProducts', duration)
    }
  }
  
  function batchUpdateProducts(newProducts: Product[]) {
    // 使用批量更新来最小化响应式开销
    const updates = new Map<string, Product>()
    
    newProducts.forEach(product => {
      updates.set(product.id, product)
    })
    
    // 单次响应式更新
    products.value = new Map([...products.value, ...updates])
  }
  
  function updateProductsFromCache(cached: any) {
    batchUpdateProducts(cached.products)
    pagination.value = cached.pagination
  }
  
  async function fetchProduct(id: string): Promise<Product | null> {
    // 检查是否已在 store 中
    if (products.value.has(id)) {
      return products.value.get(id)!
    }
    
    // 检查缓存
    const cacheKey = `product-${id}`
    if (cache.has(cacheKey)) {
      const product = cache.get(cacheKey)!
      products.value.set(id, product)
      return product
    }
    
    try {
      const response = await fetch(`/api/products/${id}`)
      const product = await response.json()
      
      products.value.set(id, product)
      cache.set(cacheKey, product)
      
      return product
    } catch (err) {
      console.error(`获取产品 ${id} 失败:`, err)
      return null
    }
  }
  
  // 防抖搜索
  const debouncedSearch = debounce((searchTerm: string) => {
    filters.value.search = searchTerm
    pagination.value.page = 1 // 重置到第一页
  }, 300)
  
  function setSearch(searchTerm: string) {
    debouncedSearch(searchTerm)
  }
  
  function setCategory(category: string) {
    filters.value.category = category
    pagination.value.page = 1
    cache.clear() // 过滤器改变时清除缓存
  }
  
  function setPriceRange(range: [number, number]) {
    filters.value.priceRange = range
    pagination.value.page = 1
    cache.clear()
  }
  
  function setPage(page: number) {
    pagination.value.page = page
  }
  
  function clearCache() {
    cache.clear()
  }
  
  // Store 销毁时清理
  function $dispose() {
    cache.destroy()
  }
  
  return {
    // 状态
    products: readonly(products),
    categories: readonly(categories),
    filters: readonly(filters),
    pagination: readonly(pagination),
    loading: readonly(loading),
    error: readonly(error),
    
    // 计算属性
    filteredProducts,
    paginatedProducts,
    totalPages,
    
    // Actions
    fetchProducts,
    fetchProduct,
    setSearch,
    setCategory,
    setPriceRange,
    setPage,
    clearCache,
    $dispose
  }
})

// 防抖工具函数
function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: number | null = null
  
  return (...args: Parameters<T>) => {
    if (timeout) {
      clearTimeout(timeout)
    }
    
    timeout = setTimeout(() => {
      func(...args)
    }, wait) as any
  }
}

性能插件

typescript
// plugins/performance.ts
import type { PiniaPlugin } from 'pinia'
import { performanceMonitor } from '@/utils/performance-monitor'

export function createPerformancePlugin(): PiniaPlugin {
  return ({ store }) => {
    const startTime = performance.now()
    
    // 监控 store 创建
    const creationTime = performance.now() - startTime
    performanceMonitor.recordStoreCreation(store.$id, creationTime)
    
    // 监控 actions
    store.$onAction(({ name, args, after, onError }) => {
      const actionStartTime = performance.now()
      
      after(() => {
        const duration = performance.now() - actionStartTime
        performanceMonitor.recordActionExecution(store.$id, name, duration)
      })
      
      onError((error) => {
        const duration = performance.now() - actionStartTime
        performanceMonitor.recordActionExecution(store.$id, `${name}:error`, duration)
      })
    })
    
    // 监控订阅
    let subscriptionCount = 0
    const originalSubscribe = store.$subscribe
    store.$subscribe = (...args) => {
      subscriptionCount++
      performanceMonitor.recordSubscriptionCount(store.$id, subscriptionCount)
      
      const unsubscribe = originalSubscribe.apply(store, args)
      
      return () => {
        subscriptionCount--
        performanceMonitor.recordSubscriptionCount(store.$id, subscriptionCount)
        return unsubscribe()
      }
    }
    
    // 估算内存使用
    const estimateMemoryUsage = () => {
      const stateSize = JSON.stringify(store.$state).length * 2 // 粗略估算
      performanceMonitor.recordMemoryUsage(store.$id, stateSize)
    }
    
    // 初始内存测量
    estimateMemoryUsage()
    
    // 定期内存测量
    const memoryInterval = setInterval(estimateMemoryUsage, 10000) // 每 10 秒
    
    return {
      $performanceMonitor: performanceMonitor,
      $clearMemoryInterval: () => clearInterval(memoryInterval)
    }
  }
}

使用示例

typescript
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createPerformancePlugin } from '@/plugins/performance'
import { performanceMonitor } from '@/utils/performance-monitor'

const app = createApp(App)
const pinia = createPinia()

// 添加性能监控
if (process.env.NODE_ENV === 'development') {
  pinia.use(createPerformancePlugin())
  performanceMonitor.startMonitoring()
}

app.use(pinia)
app.mount('#app')

// 开发环境性能报告
if (process.env.NODE_ENV === 'development') {
  // 每 30 秒记录性能报告
  setInterval(() => {
    console.log(performanceMonitor.generateReport())
  }, 30000)
}

最佳实践

  1. 懒加载: 仅在需要时加载 stores
  2. 缓存: 缓存昂贵的计算和 API 响应
  3. 防抖: 对频繁操作(如搜索)进行防抖
  4. 批量更新: 使用批量更新来最小化响应式开销
  5. 内存管理: 适当清理资源和清除缓存
  6. 虚拟滚动: 对大数据集使用虚拟滚动
  7. 监控: 在开发和生产环境中监控性能
  8. 代码分割: 将 stores 分割到单独的块中以提高加载性能
  9. 选择性响应式: 适当使用 shallowRefmarkRaw
  10. 清理: 在 store 销毁时实现适当的清理

这些优化技术确保您的 Pinia stores 即使在具有复杂状态管理需求的大型应用中也能表现良好。

Released under the MIT License.