Vue应用级性能分析与优化之Pinia状态管理优化

118 阅读6分钟

Vue应用级性能分析与优化之Pinia状态管理优化

安装 Pinia 与配置

基础安装

首先安装 Pinia 及其相关依赖:

// 使用 npm
npm install pinia

// 使用 yarn
yarn add pinia

// 使用 pnpm
pnpm add pinia

项目配置

在 Vue 3 项目中配置 Pinia:

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

// 创建 Pinia 实例
const pinia = createPinia()

// 创建应用实例
const app = createApp(App)

// 使用 Pinia
app.use(pinia)

app.mount('#app')

高级配置选项

为了提升性能和开发体验,可以添加一些插件和配置:

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
import App from './App.vue'

const pinia = createPinia()

// 开发环境下启用 Pinia DevTools
if (import.meta.env.DEV) {
  pinia.use(({ store }) => {
    store.$subscribe((mutation) => {
      console.log('Pinia mutation:', mutation)
    })
  })
}

// 添加持久化插件(可选)
pinia.use(createPersistedState({
  storage: sessionStorage,
  key: 'pinia-state'
}))

const app = createApp(App)
app.use(pinia)
app.mount('#app')

创建模块化 Store

基础 Store 结构

创建一个结构清晰、高性能的用户状态管理模块:

// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { userApi } from '@/api/user'

export const useUserStore = defineStore('user', () => {
  // State - 使用 ref 定义响应式状态
  const userInfo = ref(null)
  const token = ref(localStorage.getItem('token') || '')
  const permissions = ref([])
  const isLoading = ref(false)
  const loginTime = ref(null)

  // Getters - 使用 computed 定义计算属性
  const isAuthenticated = computed(() => !!token.value)
  const userName = computed(() => userInfo.value?.name || '未知用户')
  const userAvatar = computed(() => userInfo.value?.avatar || '/default-avatar.png')
  
  // 权限相关计算属性
  const hasAdminPermission = computed(() => 
    permissions.value.includes('admin')
  )
  
  const hasPermission = computed(() => (permission) => 
    permissions.value.includes(permission)
  )

  // Actions - 定义异步操作和状态修改方法
  async function login(credentials) {
    isLoading.value = true
    try {
      const response = await userApi.login(credentials)
      
      token.value = response.token
      userInfo.value = response.userInfo
      permissions.value = response.permissions
      loginTime.value = new Date().toISOString()
      
      // 持久化 token
      localStorage.setItem('token', token.value)
      
      return { success: true, data: response }
    } catch (error) {
      console.error('登录失败:', error)
      return { success: false, error: error.message }
    } finally {
      isLoading.value = false
    }
  }

  async function logout() {
    try {
      await userApi.logout()
    } catch (error) {
      console.error('退出登录失败:', error)
    } finally {
      // 清除状态
      token.value = ''
      userInfo.value = null
      permissions.value = []
      loginTime.value = null
      
      // 清除本地存储
      localStorage.removeItem('token')
    }
  }

  async function fetchUserInfo() {
    if (!token.value) return
    
    isLoading.value = true
    try {
      const response = await userApi.getUserInfo()
      userInfo.value = response.userInfo
      permissions.value = response.permissions
    } catch (error) {
      console.error('获取用户信息失败:', error)
      // 如果 token 无效,自动登出
      if (error.status === 401) {
        await logout()
      }
    } finally {
      isLoading.value = false
    }
  }

  // 更新用户信息
  function updateUserInfo(newInfo) {
    if (userInfo.value) {
      userInfo.value = { ...userInfo.value, ...newInfo }
    }
  }

  // 重置状态
  function $reset() {
    token.value = ''
    userInfo.value = null
    permissions.value = []
    isLoading.value = false
    loginTime.value = null
  }

  return {
    // State
    userInfo,
    token,
    permissions,
    isLoading,
    loginTime,
    
    // Getters
    isAuthenticated,
    userName,
    userAvatar,
    hasAdminPermission,
    hasPermission,
    
    // Actions
    login,
    logout,
    fetchUserInfo,
    updateUserInfo,
    $reset
  }
})

模块化 Store 架构

创建更复杂的模块化 Store 结构:

// stores/modules/app.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useAppStore = defineStore('app', () => {
  // 应用配置状态
  const theme = ref('light')
  const language = ref('zh-CN')
  const sidebarCollapsed = ref(false)
  const pageLoading = ref(false)
  const breadcrumbs = ref([])

  // 计算属性
  const isDarkTheme = computed(() => theme.value === 'dark')
  
  // Actions
  function toggleTheme() {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
    localStorage.setItem('theme', theme.value)
  }

  function toggleSidebar() {
    sidebarCollapsed.value = !sidebarCollapsed.value
  }

  function setPageLoading(loading) {
    pageLoading.value = loading
  }

  function setBreadcrumbs(crumbs) {
    breadcrumbs.value = crumbs
  }

  return {
    theme,
    language,
    sidebarCollapsed,
    pageLoading,
    breadcrumbs,
    isDarkTheme,
    toggleTheme,
    toggleSidebar,
    setPageLoading,
    setBreadcrumbs
  }
})

Store 组合模式

// stores/index.js
import { useUserStore } from './modules/user'
import { useAppStore } from './modules/app'
import { useRouterStore } from './modules/router'

// 创建根 Store 组合
export const useRootStore = () => {
  const userStore = useUserStore()
  const appStore = useAppStore()
  const routerStore = useRouterStore()

  return {
    user: userStore,
    app: appStore,
    router: routerStore
  }
}

export {
  useUserStore,
  useAppStore,
  useRouterStore
}

懒加载 Store

动态导入 Store

实现 Store 的按需加载,提升应用启动性能:

// utils/store-loader.js
const storeModules = new Map()

export async function loadStore(storeName) {
  if (storeModules.has(storeName)) {
    return storeModules.get(storeName)
  }

  let storeModule
  try {
    switch (storeName) {
      case 'user':
        storeModule = await import('@/stores/modules/user')
        break
      case 'product':
        storeModule = await import('@/stores/modules/product')
        break
      case 'order':
        storeModule = await import('@/stores/modules/order')
        break
      default:
        throw new Error(`未知的 store 模块: ${storeName}`)
    }
    
    storeModules.set(storeName, storeModule)
    return storeModule
  } catch (error) {
    console.error(`加载 ${storeName} store 失败:`, error)
    throw error
  }
}

懒加载 Hook

创建一个通用的懒加载 Hook:

// composables/useLazyStore.js
import { ref, onMounted } from 'vue'
import { loadStore } from '@/utils/store-loader'

export function useLazyStore(storeName) {
  const store = ref(null)
  const isLoading = ref(true)
  const error = ref(null)

  const loadStoreAsync = async () => {
    try {
      isLoading.value = true
      error.value = null
      
      const storeModule = await loadStore(storeName)
      const useStore = storeModule[`use${storeName.charAt(0).toUpperCase() + storeName.slice(1)}Store`]
      
      if (!useStore) {
        throw new Error(`Store ${storeName} 导出函数未找到`)
      }
      
      store.value = useStore()
    } catch (err) {
      error.value = err
      console.error(`懒加载 ${storeName} store 失败:`, err)
    } finally {
      isLoading.value = false
    }
  }

  onMounted(loadStoreAsync)

  return {
    store,
    isLoading,
    error,
    reload: loadStoreAsync
  }
}

路由级懒加载

在路由级别实现 Store 懒加载:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/user',
    name: 'User',
    component: () => import('@/views/User.vue'),
    meta: {
      requiresStore: ['user', 'app']
    }
  },
  {
    path: '/product',
    name: 'Product',
    component: () => import('@/views/Product.vue'),
    meta: {
      requiresStore: ['product', 'user']
    }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 路由守卫中预加载需要的 Store
router.beforeEach(async (to, from, next) => {
  const requiredStores = to.meta?.requiresStore || []
  
  if (requiredStores.length > 0) {
    try {
      const storePromises = requiredStores.map(storeName => loadStore(storeName))
      await Promise.all(storePromises)
    } catch (error) {
      console.error('预加载 Store 失败:', error)
    }
  }
  
  next()
})

export default router

Store 懒加载流程图

graph TD
    A[页面路由跳转] --> B{检查 meta.requiresStore}
    B -->|有依赖| C[预加载所需 Store]
    B -->|无依赖| G[直接跳转]
    C --> D{Store 已缓存?}
    D -->|是| E[返回缓存的 Store]
    D -->|否| F[动态导入 Store 模块]
    F --> H[缓存 Store 实例]
    H --> I[返回 Store 实例]
    E --> G
    I --> G
    G --> J[渲染页面组件]

在其他组件中使用 userStore

基础使用方式

在组件中使用 Pinia Store:

// components/UserProfile.vue
<template>
  <div class="user-profile">
    <div v-if="userStore.isLoading" class="loading">
      加载中...
    </div>
    <div v-else-if="userStore.isAuthenticated" class="user-info">
      <img :src="userStore.userAvatar" :alt="userStore.userName" />
      <h3>{{ userStore.userName }}</h3>
      <p>登录时间: {{ formatLoginTime }}</p>
      <button @click="handleLogout" :disabled="userStore.isLoading">
        退出登录
      </button>
    </div>
    <div v-else class="login-prompt">
      <p>请先登录</p>
      <button @click="showLoginModal">登录</button>
    </div>
  </div>
</template>

<script setup>
import { computed, onMounted } from 'vue'
import { useUserStore } from '@/stores/modules/user'
import { formatDate } from '@/utils/date'

const userStore = useUserStore()

// 计算属性
const formatLoginTime = computed(() => {
  return userStore.loginTime ? formatDate(userStore.loginTime) : ''
})

// 方法
const handleLogout = async () => {
  const result = await userStore.logout()
  if (result.success) {
    // 可以添加成功提示
    console.log('退出登录成功')
  }
}

const showLoginModal = () => {
  // 显示登录弹窗逻辑
}

// 生命周期
onMounted(() => {
  if (userStore.isAuthenticated && !userStore.userInfo) {
    userStore.fetchUserInfo()
  }
})
</script>

使用 storeToRefs 优化响应性

// components/UserDashboard.vue
<template>
  <div class="user-dashboard">
    <header class="dashboard-header">
      <h1>欢迎回来,{{ userName }}</h1>
      <div class="user-actions">
        <button @click="refreshUserInfo" :disabled="isLoading">
          刷新信息
        </button>
        <button v-if="hasAdminPermission" @click="goToAdmin">
          管理后台
        </button>
      </div>
    </header>
    
    <main class="dashboard-content">
      <div v-if="isLoading" class="loading-spinner">
        数据加载中...
      </div>
      <div v-else class="user-stats">
        <div class="stat-card" v-for="stat in userStats" :key="stat.key">
          <h3>{{ stat.label }}</h3>
          <p>{{ stat.value }}</p>
        </div>
      </div>
    </main>
  </div>
</template>

<script setup>
import { computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/modules/user'
import { useRouter } from 'vue-router'

const userStore = useUserStore()
const router = useRouter()

// 使用 storeToRefs 解构响应式状态
const { 
  userInfo, 
  isLoading, 
  userName, 
  hasAdminPermission 
} = storeToRefs(userStore)

// 计算属性
const userStats = computed(() => [
  { key: 'loginCount', label: '登录次数', value: userInfo.value?.loginCount || 0 },
  { key: 'lastLogin', label: '最后登录', value: userInfo.value?.lastLoginTime || '未知' },
  { key: 'role', label: '用户角色', value: userInfo.value?.role || '普通用户' }
])

// 方法
const refreshUserInfo = async () => {
  await userStore.fetchUserInfo()
}

const goToAdmin = () => {
  router.push('/admin')
}

onMounted(() => {
  refreshUserInfo()
})
</script>

跨组件状态共享

// components/UserNotifications.vue
<template>
  <div class="notifications">
    <div class="notification-header">
      <h3>通知消息</h3>
      <span class="badge" v-if="unreadCount > 0">{{ unreadCount }}</span>
    </div>
    <div class="notification-list">
      <div 
        v-for="notification in notifications" 
        :key="notification.id"
        :class="['notification-item', { unread: !notification.read }]"
        @click="markAsRead(notification.id)"
      >
        <p>{{ notification.message }}</p>
        <small>{{ formatDate(notification.createdAt) }}</small>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/modules/user'
import { useNotificationStore } from '@/stores/modules/notification'

const userStore = useUserStore()
const notificationStore = useNotificationStore()

const { isAuthenticated } = storeToRefs(userStore)
const { notifications } = storeToRefs(notificationStore)

// 计算属性
const unreadCount = computed(() => {
  return notifications.value.filter(n => !n.read).length
})

// 监听用户认证状态
watch(isAuthenticated, (newVal) => {
  if (newVal) {
    notificationStore.fetchNotifications()
  } else {
    notificationStore.clearNotifications()
  }
}, { immediate: true })

// 方法
const markAsRead = (notificationId) => {
  notificationStore.markAsRead(notificationId)
}
</script>

组件间通信模式

创建一个事件总线 Store:

// stores/modules/eventBus.js
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useEventBusStore = defineStore('eventBus', () => {
  const events = ref(new Map())

  function emit(eventName, payload) {
    const eventHandlers = events.value.get(eventName) || []
    eventHandlers.forEach(handler => {
      try {
        handler(payload)
      } catch (error) {
        console.error(`事件处理器执行失败 (${eventName}):`, error)
      }
    })
  }

  function on(eventName, handler) {
    const eventHandlers = events.value.get(eventName) || []
    eventHandlers.push(handler)
    events.value.set(eventName, eventHandlers)
    
    // 返回取消订阅函数
    return () => off(eventName, handler)
  }

  function off(eventName, handler) {
    const eventHandlers = events.value.get(eventName) || []
    const index = eventHandlers.indexOf(handler)
    if (index > -1) {
      eventHandlers.splice(index, 1)
      events.value.set(eventName, eventHandlers)
    }
  }

  function clear(eventName) {
    if (eventName) {
      events.value.delete(eventName)
    } else {
      events.value.clear()
    }
  }

  return {
    emit,
    on,
    off,
    clear
  }
})

手动实现状态持久化

基础持久化方案

// utils/persistence.js
export class PersistenceManager {
  constructor(options = {}) {
    this.storage = options.storage || localStorage
    this.keyPrefix = options.keyPrefix || 'pinia_'
    this.serializer = options.serializer || JSON
    this.encrypt = options.encrypt || false
    this.encryptKey = options.encryptKey || 'default_key'
  }

  // 保存状态
  saveState(key, state) {
    try {
      const serializedState = this.serializer.stringify(state)
      const dataToSave = this.encrypt ? this.encryptData(serializedState) : serializedState
      
      this.storage.setItem(this.keyPrefix + key, dataToSave)
      return true
    } catch (error) {
      console.error(`保存状态失败 (${key}):`, error)
      return false
    }
  }

  // 加载状态
  loadState(key) {
    try {
      const savedData = this.storage.getItem(this.keyPrefix + key)
      if (!savedData) return null

      const dataToDeserialize = this.encrypt ? this.decryptData(savedData) : savedData
      return this.serializer.parse(dataToDeserialize)
    } catch (error) {
      console.error(`加载状态失败 (${key}):`, error)
      return null
    }
  }

  // 删除状态
  removeState(key) {
    try {
      this.storage.removeItem(this.keyPrefix + key)
      return true
    } catch (error) {
      console.error(`删除状态失败 (${key}):`, error)
      return false
    }
  }

  // 清空所有状态
  clearAll() {
    try {
      const keysToRemove = []
      for (let i = 0; i < this.storage.length; i++) {
        const key = this.storage.key(i)
        if (key && key.startsWith(this.keyPrefix)) {
          keysToRemove.push(key)
        }
      }
      
      keysToRemove.forEach(key => this.storage.removeItem(key))
      return true
    } catch (error) {
      console.error('清空状态失败:', error)
      return false
    }
  }

  // 简单加密(实际项目中应使用更安全的加密方式)
  encryptData(data) {
    return btoa(data)
  }

  // 简单解密
  decryptData(encryptedData) {
    return atob(encryptedData)
  }
}

高级持久化插件

// plugins/persistence-plugin.js
import { PersistenceManager } from '@/utils/persistence'

export function createPersistencePlugin(options = {}) {
  const persistenceManager = new PersistenceManager({
    storage: options.storage || localStorage,
    keyPrefix: options.keyPrefix || 'pinia_store_',
    encrypt: options.encrypt || false
  })

  return ({ store }) => {
    const storeId = store.$id
    const persistConfig = options.stores?.[storeId] || {}
    
    // 如果该 store 不需要持久化,直接返回
    if (persistConfig.persist === false) {
      return
    }

    // 从本地存储恢复状态
    const savedState = persistenceManager.loadState(storeId)
    if (savedState) {
      // 只恢复指定的字段
      if (persistConfig.include) {
        const filteredState = {}
        persistConfig.include.forEach(key => {
          if (savedState[key] !== undefined) {
            filteredState[key] = savedState[key]
          }
        })
        store.$patch(filteredState)
      } else if (persistConfig.exclude) {
        const filteredState = { ...savedState }
        persistConfig.exclude.forEach(key => {
          delete filteredState[key]
        })
        store.$patch(filteredState)
      } else {
        store.$patch(savedState)
      }
    }

    // 监听状态变化并持久化
    let saveTimer = null
    const saveDelay = persistConfig.debounce || 300

    store.$subscribe((mutation, state) => {
      // 防抖保存
      if (saveTimer) {
        clearTimeout(saveTimer)
      }

      saveTimer = setTimeout(() => {
        let stateToSave = state

        // 只保存指定的字段
        if (persistConfig.include) {
          stateToSave = {}
          persistConfig.include.forEach(key => {
            if (state[key] !== undefined) {
              stateToSave[key] = state[key]
            }
          })
        } else if (persistConfig.exclude) {
          stateToSave = { ...state }
          persistConfig.exclude.forEach(key => {
            delete stateToSave[key]
          })
        }

        persistenceManager.saveState(storeId, stateToSave)
      }, saveDelay)
    }, { detached: true })

    // 提供清除持久化状态的方法
    store.$clearPersisted = () => {
      persistenceManager.removeState(storeId)
    }
  }
}

在 Store 中集成持久化

// stores/modules/user.js (优化版)
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
import { PersistenceManager } from '@/utils/persistence'

export const useUserStore = defineStore('user', () => {
  // 创建持久化管理器
  const persistence = new PersistenceManager({
    storage: localStorage,
    keyPrefix: 'user_store_'
  })

  // State
  const userInfo = ref(null)
  const token = ref('')
  const preferences = ref({
    theme: 'light',
    language: 'zh-CN',
    notifications: true
  })
  const isLoading = ref(false)

  // 从持久化存储恢复状态
  function restoreState() {
    const savedToken = persistence.loadState('token')
    const savedUserInfo = persistence.loadState('userInfo')
    const savedPreferences = persistence.loadState('preferences')

    if (savedToken) token.value = savedToken
    if (savedUserInfo) userInfo.value = savedUserInfo
    if (savedPreferences) preferences.value = { ...preferences.value, ...savedPreferences }
  }

  // 监听需要持久化的状态变化
  watch(token, (newToken) => {
    if (newToken) {
      persistence.saveState('token', newToken)
    } else {
      persistence.removeState('token')
    }
  })

  watch(userInfo, (newUserInfo) => {
    if (newUserInfo) {
      persistence.saveState('userInfo', newUserInfo)
    } else {
      persistence.removeState('userInfo')
    }
  }, { deep: true })

  watch(preferences, (newPreferences) => {
    persistence.saveState('preferences', newPreferences)
  }, { deep: true })

  // Getters
  const isAuthenticated = computed(() => !!token.value)

  // Actions
  async function login(credentials) {
    isLoading.value = true
    try {
      const response = await userApi.login(credentials)
      token.value = response.token
      userInfo.value = response.userInfo
      return { success: true, data: response }
    } catch (error) {
      return { success: false, error: error.message }
    } finally {
      isLoading.value = false
    }
  }

  function logout() {
    token.value = ''
    userInfo.value = null
    // 清除持久化数据
    persistence.removeState('token')
    persistence.removeState('userInfo')
  }

  function updatePreferences(newPrefs) {
    preferences.value = { ...preferences.value, ...newPrefs }
  }

  // 初始化时恢复状态
  restoreState()

  return {
    userInfo,
    token,
    preferences,
    isLoading,
    isAuthenticated,
    login,
    logout,
    updatePreferences
  }
})

持久化配置管理

// config/persistence.js
export const persistenceConfig = {
  // 全局配置
  global: {
    storage: localStorage,
    keyPrefix: 'pinia_',
    debounce: 300,
    encrypt: false
  },
  
  // 各 Store 的具体配置
  stores: {
    user: {
      persist: true,
      include: ['token', 'userInfo', 'preferences'],
      debounce: 100
    },
    app: {
      persist: true,
      include: ['theme', 'language', 'sidebarCollapsed'],
      debounce: 500
    },
    cart: {
      persist: true,
      exclude: ['isLoading'],
      storage: sessionStorage
    },
    temp: {
      persist: false // 临时数据不持久化
    }
  }
}

持久化流程图

graph TD
    A[Store 状态变化] --> B{需要持久化?}
    B -->|是| C[检查配置规则]
    B -->|否| H[直接更新状态]
    C --> D{有 include 配置?}
    D -->|是| E[只保存指定字段]
    D -->|否| F{有 exclude 配置?}
    F -->|是| G[排除指定字段后保存]
    F -->|否| I[保存全部状态]
    E --> J[应用防抖延迟]
    G --> J
    I --> J
    J --> K[序列化数据]
    K --> L{需要加密?}
    L -->|是| M[加密数据]
    L -->|否| N[直接存储]
    M --> N
    N --> O[写入存储介质]
    O --> P[更新完成]
    H --> P

使用示例

在 main.js 中集成持久化插件:

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createPersistencePlugin } from '@/plugins/persistence-plugin'
import { persistenceConfig } from '@/config/persistence'
import App from './App.vue'

const pinia = createPinia()

// 使用持久化插件
pinia.use(createPersistencePlugin(persistenceConfig))

const app = createApp(App)
app.use(pinia)
app.mount('#app')

通过以上完整的实现方案,您可以在 Vue 3 + Pinia 项目中实现高性能的状态管理优化。

性能优化最佳实践

状态订阅优化

// composables/useOptimizedSubscription.js
import { onUnmounted, watch, ref } from 'vue'

export function useOptimizedSubscription(store, callback, options = {}) {
  const isActive = ref(true)
  const { 
    immediate = false, 
    deep = false, 
    throttle = 0,
    debounce = 0 
  } = options

  let timer = null
  let lastExecution = 0

  const optimizedCallback = (...args) => {
    if (!isActive.value) return

    const now = Date.now()
    
    if (throttle > 0 && now - lastExecution < throttle) {
      return
    }

    if (debounce > 0) {
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        callback(...args)
        lastExecution = Date.now()
      }, debounce)
    } else {
      callback(...args)
      lastExecution = now
    }
  }

  // 使用 $subscribe 监听 store 变化
  const unsubscribe = store.$subscribe(optimizedCallback, { 
    immediate, 
    deep,
    detached: true 
  })

  // 组件卸载时清理订阅
  onUnmounted(() => {
    isActive.value = false
    unsubscribe()
    if (timer) clearTimeout(timer)
  })

  return {
    pause: () => { isActive.value = false },
    resume: () => { isActive.value = true },
    unsubscribe
  }
}

批量状态更新

// stores/modules/batchUpdate.js
import { defineStore } from 'pinia'
import { ref, nextTick } from 'vue'

export const useBatchUpdateStore = defineStore('batchUpdate', () => {
  const batchQueue = ref([])
  const isBatching = ref(false)
  const batchTimeout = ref(null)

  // 批量更新执行器
  async function executeBatch() {
    if (batchQueue.value.length === 0) return

    isBatching.value = true
    const currentBatch = [...batchQueue.value]
    batchQueue.value = []

    try {
      // 按优先级排序更新
      currentBatch.sort((a, b) => (b.priority || 0) - (a.priority || 0))
      
      // 执行批量更新
      await Promise.all(
        currentBatch.map(async ({ store, updates }) => {
          if (typeof updates === 'function') {
            await updates()
          } else {
            store.$patch(updates)
          }
        })
      )

      // 等待 DOM 更新完成
      await nextTick()
    } catch (error) {
      console.error('批量更新执行失败:', error)
    } finally {
      isBatching.value = false
    }
  }

  // 添加到批量更新队列
  function addToBatch(store, updates, options = {}) {
    const { priority = 0, immediate = false } = options

    batchQueue.value.push({
      store,
      updates,
      priority,
      timestamp: Date.now()
    })

    if (immediate) {
      executeBatch()
    } else {
      // 延迟执行批量更新
      if (batchTimeout.value) {
        clearTimeout(batchTimeout.value)
      }
      
      batchTimeout.value = setTimeout(executeBatch, 16) // 一帧的时间
    }
  }

  // 强制执行批量更新
  function flushBatch() {
    if (batchTimeout.value) {
      clearTimeout(batchTimeout.value)
      batchTimeout.value = null
    }
    return executeBatch()
  }

  return {
    batchQueue,
    isBatching,
    addToBatch,
    flushBatch,
    executeBatch
  }
})

内存泄漏防护

// utils/memoryGuard.js
export class MemoryGuard {
  constructor() {
    this.subscriptions = new Set()
    this.timers = new Set()
    this.observers = new Set()
  }

  // 注册订阅
  addSubscription(unsubscribe) {
    this.subscriptions.add(unsubscribe)
    return () => this.removeSubscription(unsubscribe)
  }

  // 移除订阅
  removeSubscription(unsubscribe) {
    if (this.subscriptions.has(unsubscribe)) {
      unsubscribe()
      this.subscriptions.delete(unsubscribe)
    }
  }

  // 注册定时器
  addTimer(timerId) {
    this.timers.add(timerId)
    return () => this.removeTimer(timerId)
  }

  // 移除定时器
  removeTimer(timerId) {
    if (this.timers.has(timerId)) {
      clearTimeout(timerId)
      clearInterval(timerId)
      this.timers.delete(timerId)
    }
  }

  // 注册观察者
  addObserver(observer) {
    this.observers.add(observer)
    return () => this.removeObserver(observer)
  }

  // 移除观察者
  removeObserver(observer) {
    if (this.observers.has(observer)) {
      if (observer.disconnect) observer.disconnect()
      if (observer.unobserve) observer.unobserve()
      this.observers.delete(observer)
    }
  }

  // 清理所有资源
  cleanup() {
    // 清理订阅
    this.subscriptions.forEach(unsubscribe => {
      try {
        unsubscribe()
      } catch (error) {
        console.error('清理订阅时出错:', error)
      }
    })
    this.subscriptions.clear()

    // 清理定时器
    this.timers.forEach(timerId => {
      try {
        clearTimeout(timerId)
        clearInterval(timerId)
      } catch (error) {
        console.error('清理定时器时出错:', error)
      }
    })
    this.timers.clear()

    // 清理观察者
    this.observers.forEach(observer => {
      try {
        if (observer.disconnect) observer.disconnect()
        if (observer.unobserve) observer.unobserve()
      } catch (error) {
        console.error('清理观察者时出错:', error)
      }
    })
    this.observers.clear()
  }
}

// 在组件中使用内存守护
export function useMemoryGuard() {
  const guard = new MemoryGuard()
  
  onUnmounted(() => {
    guard.cleanup()
  })

  return guard
}

状态缓存策略

// utils/stateCache.js
export class StateCache {
  constructor(options = {}) {
    this.maxSize = options.maxSize || 100
    this.ttl = options.ttl || 5 * 60 * 1000 // 5分钟
    this.cache = new Map()
    this.accessTimes = new Map()
    this.cleanupInterval = setInterval(() => this.cleanup(), this.ttl)
  }

  // 生成缓存键
  generateKey(storeId, params = {}) {
    const paramStr = JSON.stringify(params)
    return `${storeId}:${paramStr}`
  }

  // 设置缓存
  set(key, value, customTtl) {
    const now = Date.now()
    const expireTime = now + (customTtl || this.ttl)

    // 如果缓存已满,清理最少使用的项
    if (this.cache.size >= this.maxSize) {
      this.evictLeastUsed()
    }

    this.cache.set(key, {
      value,
      expireTime,
      createdAt: now
    })
    
    this.accessTimes.set(key, now)
  }

  // 获取缓存
  get(key) {
    const cached = this.cache.get(key)
    if (!cached) return null

    const now = Date.now()
    
    // 检查是否过期
    if (now > cached.expireTime) {
      this.delete(key)
      return null
    }

    // 更新访问时间
    this.accessTimes.set(key, now)
    return cached.value
  }

  // 删除缓存
  delete(key) {
    this.cache.delete(key)
    this.accessTimes.delete(key)
  }

  // 清理过期缓存
  cleanup() {
    const now = Date.now()
    for (const [key, cached] of this.cache.entries()) {
      if (now > cached.expireTime) {
        this.delete(key)
      }
    }
  }

  // 清理最少使用的缓存项
  evictLeastUsed() {
    let oldestKey = null
    let oldestTime = Infinity

    for (const [key, time] of this.accessTimes.entries()) {
      if (time < oldestTime) {
        oldestTime = time
        oldestKey = key
      }
    }

    if (oldestKey) {
      this.delete(oldestKey)
    }
  }

  // 清空缓存
  clear() {
    this.cache.clear()
    this.accessTimes.clear()
  }

  // 销毁缓存实例
  destroy() {
    this.clear()
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval)
    }
  }

  // 获取缓存统计信息
  getStats() {
    return {
      size: this.cache.size,
      maxSize: this.maxSize,
      hitRate: this.calculateHitRate(),
      oldestItem: this.getOldestItem()
    }
  }

  calculateHitRate() {
    // 这里可以实现命中率计算逻辑
    return 0
  }

  getOldestItem() {
    let oldest = null
    let oldestTime = Infinity

    for (const [key, cached] of this.cache.entries()) {
      if (cached.createdAt < oldestTime) {
        oldestTime = cached.createdAt
        oldest = { key, ...cached }
      }
    }

    return oldest
  }
}

异步状态管理优化

// stores/modules/async-optimization.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { StateCache } from '@/utils/stateCache'

export const useAsyncOptimizationStore = defineStore('asyncOptimization', () => {
  const cache = new StateCache({ maxSize: 50, ttl: 10 * 60 * 1000 })
  const pendingRequests = ref(new Map())
  const requestQueue = ref([])
  const isProcessingQueue = ref(false)

  // 防重复请求
  async function deduplicatedRequest(key, requestFn, options = {}) {
    const { useCache = true, cacheTime } = options

    // 检查缓存
    if (useCache) {
      const cached = cache.get(key)
      if (cached) return cached
    }

    // 检查是否有相同的请求正在进行
    if (pendingRequests.value.has(key)) {
      return pendingRequests.value.get(key)
    }

    // 创建新请求
    const requestPromise = requestFn()
      .then(result => {
        if (useCache) {
          cache.set(key, result, cacheTime)
        }
        return result
      })
      .finally(() => {
        pendingRequests.value.delete(key)
      })

    pendingRequests.value.set(key, requestPromise)
    return requestPromise
  }

  // 请求队列管理
  async function queueRequest(requestFn, priority = 0) {
    return new Promise((resolve, reject) => {
      requestQueue.value.push({
        requestFn,
        priority,
        resolve,
        reject,
        timestamp: Date.now()
      })

      // 按优先级排序
      requestQueue.value.sort((a, b) => b.priority - a.priority)

      if (!isProcessingQueue.value) {
        processQueue()
      }
    })
  }

  // 处理请求队列
  async function processQueue() {
    if (isProcessingQueue.value || requestQueue.value.length === 0) return

    isProcessingQueue.value = true

    while (requestQueue.value.length > 0) {
      const { requestFn, resolve, reject } = requestQueue.value.shift()

      try {
        const result = await requestFn()
        resolve(result)
      } catch (error) {
        reject(error)
      }

      // 让出控制权,避免阻塞主线程
      await new Promise(resolve => setTimeout(resolve, 0))
    }

    isProcessingQueue.value = false
  }

  // 批量请求优化
  async function batchRequest(requests, options = {}) {
    const { 
      batchSize = 5, 
      delay = 100,
      retryCount = 3 
    } = options

    const results = []
    const errors = []

    for (let i = 0; i < requests.length; i += batchSize) {
      const batch = requests.slice(i, i + batchSize)
      
      try {
        const batchPromises = batch.map(async (request, index) => {
          let attempts = 0
          while (attempts < retryCount) {
            try {
              return await request()
            } catch (error) {
              attempts++
              if (attempts === retryCount) throw error
              await new Promise(resolve => setTimeout(resolve, delay * attempts))
            }
          }
        })

        const batchResults = await Promise.allSettled(batchPromises)
        
        batchResults.forEach((result, index) => {
          if (result.status === 'fulfilled') {
            results[i + index] = result.value
          } else {
            errors[i + index] = result.reason
          }
        })

        // 批次间延迟
        if (i + batchSize < requests.length) {
          await new Promise(resolve => setTimeout(resolve, delay))
        }
      } catch (error) {
        console.error('批量请求失败:', error)
        break
      }
    }

    return { results, errors }
  }

  return {
    deduplicatedRequest,
    queueRequest,
    batchRequest,
    cache,
    pendingRequests: computed(() => pendingRequests.value),
    queueLength: computed(() => requestQueue.value.length),
    isProcessingQueue: computed(() => isProcessingQueue.value)
  }
})

性能监控和分析

// utils/performanceMonitor.js
export class PerformanceMonitor {
  constructor() {
    this.metrics = new Map()
    this.observers = []
    this.isEnabled = true
  }

  // 开始性能测量
  start(key) {
    if (!this.isEnabled) return

    this.metrics.set(key, {
      startTime: performance.now(),
      startMemory: this.getMemoryUsage()
    })
  }

  // 结束性能测量
  end(key) {
    if (!this.isEnabled) return

    const startData = this.metrics.get(key)
    if (!startData) return

    const endTime = performance.now()
    const endMemory = this.getMemoryUsage()

    const result = {
      key,
      duration: endTime - startData.startTime,
      memoryDelta: endMemory - startData.startMemory,
      timestamp: Date.now()
    }

    // 通知观察者
    this.notifyObservers(result)

    this.metrics.delete(key)
    return result
  }

  // 测量异步操作
  async measure(key, asyncFn) {
    this.start(key)
    try {
      const result = await asyncFn()
      return result
    } finally {
      this.end(key)
    }
  }

  // 获取内存使用情况
  getMemoryUsage() {
    if (performance.memory) {
      return performance.memory.usedJSHeapSize
    }
    return 0
  }

  // 添加观察者
  addObserver(observer) {
    this.observers.push(observer)
  }

  // 移除观察者
  removeObserver(observer) {
    const index = this.observers.indexOf(observer)
    if (index > -1) {
      this.observers.splice(index, 1)
    }
  }

  // 通知观察者
  notifyObservers(data) {
    this.observers.forEach(observer => {
      try {
        observer(data)
      } catch (error) {
        console.error('性能监控观察者执行失败:', error)
      }
    })
  }

  // 启用/禁用监控
  setEnabled(enabled) {
    this.isEnabled = enabled
  }

  // 获取性能报告
  getReport() {
    return {
      activeMetrics: Array.from(this.metrics.keys()),
      memoryUsage: this.getMemoryUsage(),
      timestamp: Date.now()
    }
  }
}

// Store 性能监控插件
export function createPerformancePlugin() {
  const monitor = new PerformanceMonitor()

  // 监控结果处理
  monitor.addObserver((data) => {
    if (data.duration > 100) { // 超过100ms的操作
      console.warn(`Store操作性能警告: ${data.key} 耗时 ${data.duration.toFixed(2)}ms`)
    }

    if (data.memoryDelta > 1024 * 1024) { // 内存增长超过1MB
      console.warn(`Store操作内存警告: ${data.key} 内存增长 ${(data.memoryDelta / 1024 / 1024).toFixed(2)}MB`)
    }
  })

  return ({ store }) => {
    const originalPatch = store.$patch

    // 监控 $patch 操作
    store.$patch = function(...args) {
      const key = `${store.$id}.$patch`
      monitor.start(key)
      
      try {
        return originalPatch.apply(this, args)
      } finally {
        monitor.end(key)
      }
    }

    // 监控 actions
    Object.keys(store).forEach(key => {
      const value = store[key]
      if (typeof value === 'function' && !key.startsWith(')) {
        const originalAction = value

        store[key] = async function(...args) {
          const actionKey = `${store.$id}.${key}`
          return monitor.measure(actionKey, () => originalAction.apply(this, args))
        }
      }
    })

    // 添加监控方法到 store
    store.$performanceMonitor = monitor
  }
}

完整的优化应用示例

// main.js - 完整配置
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createPersistencePlugin } from '@/plugins/persistence-plugin'
import { createPerformancePlugin } from '@/utils/performanceMonitor'
import { persistenceConfig } from '@/config/persistence'
import App from './App.vue'

const pinia = createPinia()

// 开发环境性能监控
if (import.meta.env.DEV) {
  pinia.use(createPerformancePlugin())
}

// 持久化插件
pinia.use(createPersistencePlugin(persistenceConfig))

// 全局错误处理
pinia.use(({ store }) => {
  store.$onError((error, vm, info) => {
    console.error(`Store ${store.$id} 错误:`, error)
    // 可以发送错误到监控系统
  })
})

const app = createApp(App)
app.use(pinia)
app.mount('#app')

总结

本文档详细介绍了 Vue 3 + Pinia 状态管理的完整优化方案,涵盖了从基础配置到高级性能优化的各个方面:

  1. 模块化架构: 通过合理的 Store 结构设计,实现代码的可维护性和可扩展性
  2. 懒加载策略: 按需加载 Store 模块,减少应用启动时间和内存占用
  3. 组件集成: 展示了在不同场景下如何高效使用 Pinia Store
  4. 持久化方案: 实现了灵活可配置的状态持久化机制
  5. 性能优化: 包括防抖节流、批量更新、内存管理等高级优化技术
  6. 监控和分析: 提供了完整的性能监控和问题诊断工具

这套方案适用于中大型 Vue 3 项目,能够有效提升应用性能和开发效率。