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 状态管理的完整优化方案,涵盖了从基础配置到高级性能优化的各个方面:
- 模块化架构: 通过合理的 Store 结构设计,实现代码的可维护性和可扩展性
- 懒加载策略: 按需加载 Store 模块,减少应用启动时间和内存占用
- 组件集成: 展示了在不同场景下如何高效使用 Pinia Store
- 持久化方案: 实现了灵活可配置的状态持久化机制
- 性能优化: 包括防抖节流、批量更新、内存管理等高级优化技术
- 监控和分析: 提供了完整的性能监控和问题诊断工具
这套方案适用于中大型 Vue 3 项目,能够有效提升应用性能和开发效率。