性能优化
全面的 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)
}
最佳实践
- 懒加载: 仅在需要时加载 stores
- 缓存: 缓存昂贵的计算和 API 响应
- 防抖: 对频繁操作(如搜索)进行防抖
- 批量更新: 使用批量更新来最小化响应式开销
- 内存管理: 适当清理资源和清除缓存
- 虚拟滚动: 对大数据集使用虚拟滚动
- 监控: 在开发和生产环境中监控性能
- 代码分割: 将 stores 分割到单独的块中以提高加载性能
- 选择性响应式: 适当使用
shallowRef
和markRaw
- 清理: 在 store 销毁时实现适当的清理
这些优化技术确保您的 Pinia stores 即使在具有复杂状态管理需求的大型应用中也能表现良好。