Vue 3 状态管理进阶:Pinia 高级用法与插件开发实战
Orion K Lv6

Pinia 作为 Vue 3 官方推荐的状态管理库,不仅提供了简洁的 API,还具备强大的扩展能力。本文将深入探讨 Pinia 的高级用法,包括插件开发、状态持久化、性能优化和最佳实践,帮助开发者构建更加健壮和高效的状态管理系统。

Pinia 核心架构深入

1. Store 的内部机制

Store 生命周期管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// stores/core/StoreManager.ts
import { App, markRaw } from 'vue'
import { createPinia, Pinia, Store, StoreDefinition } from 'pinia'
import type { StateTree, _GettersTree, _ActionsTree } from 'pinia'

/**
* Store 管理器
* 提供 Store 的创建、销毁、热重载等功能
*/
export class StoreManager {
private static instance: StoreManager
private pinia: Pinia
private stores = new Map<string, Store>()
private storeDefinitions = new Map<string, StoreDefinition>()

private constructor() {
this.pinia = createPinia()
this.setupDevtools()
}

/**
* 获取单例实例
*/
static getInstance(): StoreManager {
if (!StoreManager.instance) {
StoreManager.instance = new StoreManager()
}
return StoreManager.instance
}

/**
* 安装到 Vue 应用
*/
install(app: App): void {
app.use(this.pinia)
}

/**
* 注册 Store 定义
*/
registerStore<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A extends _ActionsTree
>(id: Id, definition: StoreDefinition<Id, S, G, A>): void {
this.storeDefinitions.set(id, definition)
}

/**
* 获取 Store 实例
*/
getStore<T extends Store>(id: string): T | undefined {
return this.stores.get(id) as T
}

/**
* 创建 Store 实例
*/
createStore<T extends Store>(id: string): T | undefined {
const definition = this.storeDefinitions.get(id)
if (!definition) {
console.warn(`Store definition not found: ${id}`)
return undefined
}

const store = definition() as T
this.stores.set(id, store)

// 添加销毁方法
;(store as any).$dispose = () => {
this.destroyStore(id)
}

return store
}

/**
* 销毁 Store 实例
*/
destroyStore(id: string): void {
const store = this.stores.get(id)
if (store) {
// 清理订阅和副作用
if (typeof (store as any).$dispose === 'function') {
;(store as any).$dispose()
}
this.stores.delete(id)
}
}

/**
* 热重载 Store
*/
hotReloadStore<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A extends _ActionsTree
>(id: Id, definition: StoreDefinition<Id, S, G, A>): void {
if (process.env.NODE_ENV === 'development') {
const existingStore = this.stores.get(id)
if (existingStore) {
// 保存当前状态
const currentState = { ...existingStore.$state }

// 销毁旧实例
this.destroyStore(id)

// 注册新定义
this.registerStore(id, definition)

// 创建新实例
const newStore = this.createStore(id)
if (newStore) {
// 恢复状态
newStore.$patch(currentState)
}
}
}
}

/**
* 设置开发工具
*/
private setupDevtools(): void {
if (process.env.NODE_ENV === 'development') {
// 添加开发工具支持
this.pinia.use(({ store }) => {
store._customProperties = markRaw(new Set())

// 添加调试信息
;(store as any)._debug = {
created: Date.now(),
actions: new Map(),
mutations: new Map()
}
})
}
}

/**
* 获取所有 Store 状态
*/
getAllStates(): Record<string, any> {
const states: Record<string, any> = {}
this.stores.forEach((store, id) => {
states[id] = store.$state
})
return states
}

/**
* 重置所有 Store
*/
resetAllStores(): void {
this.stores.forEach(store => {
if (typeof store.$reset === 'function') {
store.$reset()
}
})
}
}

// 导出单例实例
export const storeManager = StoreManager.getInstance()

2. 高级 Store 模式

组合式 Store 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
// stores/composables/useUserStore.ts
import { ref, computed, watch } from 'vue'
import { defineStore, acceptHMRUpdate } from 'pinia'
import { useLocalStorage, useSessionStorage } from '@vueuse/core'
import type { User, UserPreferences, LoginCredentials } from '@/types/user'

/**
* 用户状态管理 Store
* 使用组合式 API 模式
*/
export const useUserStore = defineStore('user', () => {
// ===== 状态定义 =====
const user = ref<User | null>(null)
const token = ref<string>('')
const refreshToken = ref<string>('')
const permissions = ref<string[]>([])
const preferences = useLocalStorage<UserPreferences>('user-preferences', {
theme: 'light',
language: 'zh-CN',
timezone: 'Asia/Shanghai'
})

// 会话状态
const isLoading = ref(false)
const lastActivity = useSessionStorage('last-activity', Date.now())
const sessionTimeout = ref(30 * 60 * 1000) // 30分钟

// ===== 计算属性 =====
const isAuthenticated = computed(() => {
return !!token.value && !!user.value
})

const isAdmin = computed(() => {
return permissions.value.includes('admin')
})

const userDisplayName = computed(() => {
if (!user.value) return ''
return user.value.nickname || user.value.username || user.value.email
})

const isSessionExpired = computed(() => {
return Date.now() - lastActivity.value > sessionTimeout.value
})

const userAvatar = computed(() => {
return user.value?.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.value?.username}`
})

// ===== 操作方法 =====

/**
* 用户登录
*/
const login = async (credentials: LoginCredentials): Promise<void> => {
try {
isLoading.value = true

const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
})

if (!response.ok) {
throw new Error('登录失败')
}

const data = await response.json()

// 设置用户信息
user.value = data.user
token.value = data.token
refreshToken.value = data.refreshToken
permissions.value = data.permissions || []

// 更新最后活动时间
updateLastActivity()

// 启动会话监控
startSessionMonitoring()

} catch (error) {
console.error('Login error:', error)
throw error
} finally {
isLoading.value = false
}
}

/**
* 用户登出
*/
const logout = async (): Promise<void> => {
try {
if (token.value) {
await fetch('/api/auth/logout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token.value}`
}
})
}
} catch (error) {
console.error('Logout error:', error)
} finally {
// 清理状态
clearUserData()
}
}

/**
* 刷新令牌
*/
const refreshAccessToken = async (): Promise<boolean> => {
try {
if (!refreshToken.value) {
return false
}

const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refreshToken: refreshToken.value
})
})

if (!response.ok) {
return false
}

const data = await response.json()
token.value = data.token

return true
} catch (error) {
console.error('Token refresh error:', error)
return false
}
}

/**
* 更新用户信息
*/
const updateProfile = async (updates: Partial<User>): Promise<void> => {
try {
isLoading.value = true

const response = await fetch('/api/user/profile', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token.value}`
},
body: JSON.stringify(updates)
})

if (!response.ok) {
throw new Error('更新失败')
}

const updatedUser = await response.json()
user.value = { ...user.value, ...updatedUser }

} catch (error) {
console.error('Profile update error:', error)
throw error
} finally {
isLoading.value = false
}
}

/**
* 更新用户偏好设置
*/
const updatePreferences = (updates: Partial<UserPreferences>): void => {
preferences.value = { ...preferences.value, ...updates }
}

/**
* 检查权限
*/
const hasPermission = (permission: string): boolean => {
return permissions.value.includes(permission) || isAdmin.value
}

/**
* 检查多个权限(AND 逻辑)
*/
const hasAllPermissions = (requiredPermissions: string[]): boolean => {
return requiredPermissions.every(permission => hasPermission(permission))
}

/**
* 检查多个权限(OR 逻辑)
*/
const hasAnyPermission = (requiredPermissions: string[]): boolean => {
return requiredPermissions.some(permission => hasPermission(permission))
}

/**
* 更新最后活动时间
*/
const updateLastActivity = (): void => {
lastActivity.value = Date.now()
}

/**
* 启动会话监控
*/
const startSessionMonitoring = (): void => {
// 监听用户活动
const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart']

const activityHandler = () => {
updateLastActivity()
}

events.forEach(event => {
document.addEventListener(event, activityHandler, true)
})

// 定期检查会话状态
const sessionCheckInterval = setInterval(() => {
if (isSessionExpired.value && isAuthenticated.value) {
logout()
clearInterval(sessionCheckInterval)
}
}, 60000) // 每分钟检查一次
}

/**
* 清理用户数据
*/
const clearUserData = (): void => {
user.value = null
token.value = ''
refreshToken.value = ''
permissions.value = []
lastActivity.value = 0
}

/**
* 初始化用户状态
*/
const initializeUser = async (): Promise<void> => {
if (token.value && !isSessionExpired.value) {
try {
const response = await fetch('/api/user/me', {
headers: {
'Authorization': `Bearer ${token.value}`
}
})

if (response.ok) {
const userData = await response.json()
user.value = userData.user
permissions.value = userData.permissions || []
startSessionMonitoring()
} else {
// 尝试刷新令牌
const refreshed = await refreshAccessToken()
if (!refreshed) {
clearUserData()
}
}
} catch (error) {
console.error('User initialization error:', error)
clearUserData()
}
}
}

// ===== 监听器 =====

// 监听令牌变化,自动设置请求头
watch(token, (newToken) => {
if (newToken) {
// 设置全局请求头
const event = new CustomEvent('token-updated', {
detail: { token: newToken }
})
window.dispatchEvent(event)
}
})

// 监听主题变化
watch(() => preferences.value.theme, (newTheme) => {
document.documentElement.setAttribute('data-theme', newTheme)
}, { immediate: true })

return {
// 状态
user: readonly(user),
token: readonly(token),
permissions: readonly(permissions),
preferences,
isLoading: readonly(isLoading),
lastActivity: readonly(lastActivity),

// 计算属性
isAuthenticated,
isAdmin,
userDisplayName,
userAvatar,
isSessionExpired,

// 方法
login,
logout,
refreshAccessToken,
updateProfile,
updatePreferences,
hasPermission,
hasAllPermissions,
hasAnyPermission,
updateLastActivity,
initializeUser
}
})

// 热重载支持
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
}

Pinia 插件开发

1. 状态持久化插件

高级持久化插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// plugins/pinia-persistence.ts
import { PiniaPluginContext, StateTree } from 'pinia'
import { watch, nextTick } from 'vue'

/**
* 持久化配置接口
*/
export interface PersistenceOptions {
// 存储键名
key?: string
// 存储引擎
storage?: Storage
// 需要持久化的路径
paths?: string[]
// 排除的路径
excludePaths?: string[]
// 序列化函数
serializer?: {
serialize: (value: any) => string
deserialize: (value: string) => any
}
// 加密选项
encryption?: {
encrypt: (value: string) => string
decrypt: (value: string) => string
}
// 版本控制
version?: number
// 迁移函数
migrate?: (persistedState: any, version: number) => any
// 调试模式
debug?: boolean
// 防抖延迟
debounce?: number
// 条件持久化
condition?: (state: StateTree) => boolean
}

/**
* 默认序列化器
*/
const defaultSerializer = {
serialize: JSON.stringify,
deserialize: JSON.parse
}

/**
* 获取嵌套对象的值
*/
function getNestedValue(obj: any, path: string): any {
return path.split('.').reduce((current, key) => current?.[key], obj)
}

/**
* 设置嵌套对象的值
*/
function setNestedValue(obj: any, path: string, value: any): void {
const keys = path.split('.')
const lastKey = keys.pop()!
const target = keys.reduce((current, key) => {
if (!(key in current)) {
current[key] = {}
}
return current[key]
}, obj)
target[lastKey] = value
}

/**
* 过滤状态对象
*/
function filterState(
state: StateTree,
paths?: string[],
excludePaths?: string[]
): Partial<StateTree> {
if (!paths && !excludePaths) {
return state
}

const result: any = {}

if (paths) {
// 只包含指定路径
paths.forEach(path => {
const value = getNestedValue(state, path)
if (value !== undefined) {
setNestedValue(result, path, value)
}
})
} else {
// 排除指定路径
Object.keys(state).forEach(key => {
if (!excludePaths?.includes(key)) {
result[key] = state[key]
}
})
}

return result
}

/**
* 防抖函数
*/
function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout
return (...args: Parameters<T>) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func(...args), delay)
}
}

/**
* 创建持久化插件
*/
export function createPersistencePlugin(globalOptions: PersistenceOptions = {}) {
return ({ store, options }: PiniaPluginContext) => {
// 获取持久化配置
const persistOptions = (options as any).persist
if (!persistOptions) {
return
}

// 合并配置
const config: Required<PersistenceOptions> = {
key: store.$id,
storage: localStorage,
paths: [],
excludePaths: [],
serializer: defaultSerializer,
encryption: null as any,
version: 1,
migrate: (state) => state,
debug: false,
debounce: 300,
condition: () => true,
...globalOptions,
...(typeof persistOptions === 'object' ? persistOptions : {})
}

const {
key,
storage,
paths,
excludePaths,
serializer,
encryption,
version,
migrate,
debug,
debounce: debounceDelay,
condition
} = config

/**
* 从存储中恢复状态
*/
const restoreState = (): void => {
try {
const stored = storage.getItem(key)
if (!stored) {
if (debug) {
console.log(`[Pinia Persistence] No stored state found for ${key}`)
}
return
}

let decrypted = stored
if (encryption) {
decrypted = encryption.decrypt(stored)
}

const parsed = serializer.deserialize(decrypted)

// 版本检查和迁移
if (parsed._version !== version) {
if (debug) {
console.log(`[Pinia Persistence] Migrating state from version ${parsed._version} to ${version}`)
}
const migrated = migrate(parsed.state, parsed._version || 0)
store.$patch(migrated)
} else {
store.$patch(parsed.state)
}

if (debug) {
console.log(`[Pinia Persistence] State restored for ${key}:`, parsed.state)
}
} catch (error) {
console.error(`[Pinia Persistence] Failed to restore state for ${key}:`, error)
// 清理损坏的数据
storage.removeItem(key)
}
}

/**
* 保存状态到存储
*/
const saveState = (): void => {
try {
if (!condition(store.$state)) {
if (debug) {
console.log(`[Pinia Persistence] Condition not met, skipping save for ${key}`)
}
return
}

const stateToSave = filterState(store.$state, paths, excludePaths)

const dataToStore = {
state: stateToSave,
_version: version,
_timestamp: Date.now()
}

let serialized = serializer.serialize(dataToStore)

if (encryption) {
serialized = encryption.encrypt(serialized)
}

storage.setItem(key, serialized)

if (debug) {
console.log(`[Pinia Persistence] State saved for ${key}:`, stateToSave)
}
} catch (error) {
console.error(`[Pinia Persistence] Failed to save state for ${key}:`, error)
}
}

// 创建防抖保存函数
const debouncedSave = debounce(saveState, debounceDelay)

// 恢复状态
restoreState()

// 监听状态变化
store.$subscribe(
(mutation, state) => {
if (debug) {
console.log(`[Pinia Persistence] State changed for ${key}:`, mutation)
}
debouncedSave()
},
{ detached: true }
)

// 添加清理方法
store.$clearPersistence = () => {
storage.removeItem(key)
if (debug) {
console.log(`[Pinia Persistence] Cleared persistence for ${key}`)
}
}

// 添加手动保存方法
store.$savePersistence = () => {
saveState()
}
}
}

/**
* 加密工具
*/
export class SimpleEncryption {
private key: string

constructor(key: string) {
this.key = key
}

/**
* 简单的 XOR 加密
*/
encrypt(text: string): string {
let result = ''
for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(
text.charCodeAt(i) ^ this.key.charCodeAt(i % this.key.length)
)
}
return btoa(result)
}

/**
* 简单的 XOR 解密
*/
decrypt(encryptedText: string): string {
const text = atob(encryptedText)
let result = ''
for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(
text.charCodeAt(i) ^ this.key.charCodeAt(i % this.key.length)
)
}
return result
}
}

/**
* 压缩序列化器
*/
export const compressedSerializer = {
serialize: (value: any): string => {
const json = JSON.stringify(value)
// 这里可以集成 LZ-string 或其他压缩库
return json
},
deserialize: (value: string): any => {
// 这里可以集成对应的解压缩
return JSON.parse(value)
}
}

2. 状态同步插件

跨标签页状态同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// plugins/pinia-sync.ts
import { PiniaPluginContext } from 'pinia'
import { watch } from 'vue'

/**
* 同步配置接口
*/
export interface SyncOptions {
// 同步键名
key?: string
// 需要同步的路径
paths?: string[]
// 排除的路径
excludePaths?: string[]
// 是否启用
enabled?: boolean
// 调试模式
debug?: boolean
// 同步延迟
debounce?: number
// 冲突解决策略
conflictResolution?: 'timestamp' | 'manual' | 'merge'
}

/**
* 同步事件类型
*/
interface SyncEvent {
type: 'state-update'
storeId: string
state: any
timestamp: number
source: string
}

/**
* 创建状态同步插件
*/
export function createSyncPlugin(globalOptions: SyncOptions = {}) {
const instanceId = Math.random().toString(36).substr(2, 9)

return ({ store, options }: PiniaPluginContext) => {
const syncOptions = (options as any).sync
if (!syncOptions) {
return
}

const config: Required<SyncOptions> = {
key: `pinia-sync-${store.$id}`,
paths: [],
excludePaths: [],
enabled: true,
debug: false,
debounce: 100,
conflictResolution: 'timestamp',
...globalOptions,
...(typeof syncOptions === 'object' ? syncOptions : {})
}

if (!config.enabled) {
return
}

const {
key,
paths,
excludePaths,
debug,
debounce: debounceDelay,
conflictResolution
} = config

let lastSyncTimestamp = 0

/**
* 过滤需要同步的状态
*/
const filterSyncState = (state: any): any => {
if (paths.length > 0) {
const filtered: any = {}
paths.forEach(path => {
const value = getNestedValue(state, path)
if (value !== undefined) {
setNestedValue(filtered, path, value)
}
})
return filtered
}

if (excludePaths.length > 0) {
const filtered = { ...state }
excludePaths.forEach(path => {
deleteNestedValue(filtered, path)
})
return filtered
}

return state
}

/**
* 删除嵌套值
*/
const deleteNestedValue = (obj: any, path: string): void => {
const keys = path.split('.')
const lastKey = keys.pop()!
const target = keys.reduce((current, key) => current?.[key], obj)
if (target) {
delete target[lastKey]
}
}

/**
* 获取嵌套值
*/
const getNestedValue = (obj: any, path: string): any => {
return path.split('.').reduce((current, key) => current?.[key], obj)
}

/**
* 设置嵌套值
*/
const setNestedValue = (obj: any, path: string, value: any): void => {
const keys = path.split('.')
const lastKey = keys.pop()!
const target = keys.reduce((current, key) => {
if (!(key in current)) {
current[key] = {}
}
return current[key]
}, obj)
target[lastKey] = value
}

/**
* 广播状态更新
*/
const broadcastUpdate = debounce((state: any) => {
const syncState = filterSyncState(state)
const event: SyncEvent = {
type: 'state-update',
storeId: store.$id,
state: syncState,
timestamp: Date.now(),
source: instanceId
}

localStorage.setItem(key, JSON.stringify(event))

if (debug) {
console.log(`[Pinia Sync] Broadcasting update for ${store.$id}:`, syncState)
}
}, debounceDelay)

/**
* 处理接收到的状态更新
*/
const handleStorageChange = (e: StorageEvent) => {
if (e.key !== key || !e.newValue) {
return
}

try {
const event: SyncEvent = JSON.parse(e.newValue)

// 忽略自己发送的事件
if (event.source === instanceId) {
return
}

// 忽略其他 store 的事件
if (event.storeId !== store.$id) {
return
}

// 冲突解决
if (conflictResolution === 'timestamp' && event.timestamp <= lastSyncTimestamp) {
if (debug) {
console.log(`[Pinia Sync] Ignoring outdated update for ${store.$id}`)
}
return
}

lastSyncTimestamp = event.timestamp

// 应用状态更新
store.$patch(event.state)

if (debug) {
console.log(`[Pinia Sync] Applied update for ${store.$id}:`, event.state)
}
} catch (error) {
console.error(`[Pinia Sync] Failed to handle storage change:`, error)
}
}

/**
* 防抖函数
*/
function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout
return (...args: Parameters<T>) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func(...args), delay)
}
}

// 监听状态变化
store.$subscribe(
(mutation, state) => {
broadcastUpdate(state)
},
{ detached: true }
)

// 监听存储变化
window.addEventListener('storage', handleStorageChange)

// 清理函数
const cleanup = () => {
window.removeEventListener('storage', handleStorageChange)
}

// 添加到 store 实例
;(store as any).$syncCleanup = cleanup
}
}

3. 性能监控插件

状态性能分析插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// plugins/pinia-performance.ts
import { PiniaPluginContext } from 'pinia'
import { ref, computed } from 'vue'

/**
* 性能监控配置
*/
export interface PerformanceOptions {
// 是否启用
enabled?: boolean
// 采样率 (0-1)
sampleRate?: number
// 慢操作阈值 (ms)
slowThreshold?: number
// 内存监控
memoryMonitoring?: boolean
// 自动报告间隔 (ms)
reportInterval?: number
// 报告回调
onReport?: (report: PerformanceReport) => void
}

/**
* 性能报告接口
*/
export interface PerformanceReport {
storeId: string
totalMutations: number
slowMutations: number
averageTime: number
maxTime: number
minTime: number
memoryUsage?: {
used: number
total: number
percentage: number
}
topSlowMutations: Array<{
type: string
time: number
timestamp: number
}>
}

/**
* 创建性能监控插件
*/
export function createPerformancePlugin(globalOptions: PerformanceOptions = {}) {
return ({ store, options }: PiniaPluginContext) => {
const perfOptions = (options as any).performance
if (!perfOptions) {
return
}

const config: Required<PerformanceOptions> = {
enabled: true,
sampleRate: 1.0,
slowThreshold: 16, // 16ms (60fps)
memoryMonitoring: false,
reportInterval: 60000, // 1分钟
onReport: () => {},
...globalOptions,
...(typeof perfOptions === 'object' ? perfOptions : {})
}

if (!config.enabled) {
return
}

// 性能数据收集
const mutationTimes: number[] = []
const slowMutations: Array<{
type: string
time: number
timestamp: number
}> = []

let totalMutations = 0
let reportTimer: NodeJS.Timeout

/**
* 获取内存使用情况
*/
const getMemoryUsage = () => {
if (!config.memoryMonitoring || !(performance as any).memory) {
return undefined
}

const memory = (performance as any).memory
return {
used: memory.usedJSHeapSize,
total: memory.totalJSHeapSize,
percentage: (memory.usedJSHeapSize / memory.totalJSHeapSize) * 100
}
}

/**
* 生成性能报告
*/
const generateReport = (): PerformanceReport => {
const report: PerformanceReport = {
storeId: store.$id,
totalMutations,
slowMutations: slowMutations.length,
averageTime: mutationTimes.length > 0
? mutationTimes.reduce((a, b) => a + b, 0) / mutationTimes.length
: 0,
maxTime: mutationTimes.length > 0 ? Math.max(...mutationTimes) : 0,
minTime: mutationTimes.length > 0 ? Math.min(...mutationTimes) : 0,
memoryUsage: getMemoryUsage(),
topSlowMutations: slowMutations
.sort((a, b) => b.time - a.time)
.slice(0, 10)
}

return report
}

/**
* 重置统计数据
*/
const resetStats = () => {
mutationTimes.length = 0
slowMutations.length = 0
totalMutations = 0
}

// 监听状态变化
store.$subscribe(
(mutation, state) => {
// 采样检查
if (Math.random() > config.sampleRate) {
return
}

const startTime = performance.now()

// 使用 nextTick 确保状态更新完成后测量
Promise.resolve().then(() => {
const endTime = performance.now()
const duration = endTime - startTime

totalMutations++
mutationTimes.push(duration)

// 记录慢操作
if (duration > config.slowThreshold) {
slowMutations.push({
type: mutation.type,
time: duration,
timestamp: Date.now()
})

console.warn(
`[Pinia Performance] Slow mutation detected in ${store.$id}:`,
{
type: mutation.type,
duration: `${duration.toFixed(2)}ms`,
threshold: `${config.slowThreshold}ms`
}
)
}

// 限制数组大小
if (mutationTimes.length > 1000) {
mutationTimes.splice(0, 500)
}
if (slowMutations.length > 100) {
slowMutations.splice(0, 50)
}
})
},
{ detached: true }
)

// 定期报告
if (config.reportInterval > 0) {
reportTimer = setInterval(() => {
const report = generateReport()
config.onReport(report)

// 可选:重置统计数据
// resetStats()
}, config.reportInterval)
}

// 添加方法到 store
;(store as any).$getPerformanceReport = generateReport
;(store as any).$resetPerformanceStats = resetStats

// 清理函数
const cleanup = () => {
if (reportTimer) {
clearInterval(reportTimer)
}
}

;(store as any).$performanceCleanup = cleanup
}
}

/**
* 性能监控组合式函数
*/
export function useStorePerformance(storeId: string) {
const performanceData = ref<PerformanceReport | null>(null)
const isMonitoring = ref(false)

const startMonitoring = () => {
isMonitoring.value = true
// 实现监控逻辑
}

const stopMonitoring = () => {
isMonitoring.value = false
}

const getReport = () => {
// 获取性能报告
return performanceData.value
}

const averageResponseTime = computed(() => {
return performanceData.value?.averageTime || 0
})

const slowOperationsCount = computed(() => {
return performanceData.value?.slowMutations || 0
})

return {
performanceData: readonly(performanceData),
isMonitoring: readonly(isMonitoring),
averageResponseTime,
slowOperationsCount,
startMonitoring,
stopMonitoring,
getReport
}
}

总结

Vue 3 结合 Pinia 提供了强大而灵活的状态管理解决方案。通过插件系统的扩展,可以实现状态持久化、跨标签页同步、性能监控等高级功能: 1 2 3

核心优势

  1. 组合式 API 支持:更好的 TypeScript 集成和代码组织
  2. 插件生态:丰富的插件系统支持各种扩展需求
  3. 性能优化:内置的性能监控和优化机制
  4. 开发体验:优秀的开发工具支持和热重载
  5. 类型安全:完整的 TypeScript 类型推导

最佳实践

  1. 模块化设计:按功能划分 Store,保持单一职责
  2. 状态持久化:合理配置持久化策略,避免敏感数据泄露 4
  3. 性能监控:在生产环境中启用性能监控,及时发现问题
  4. 插件开发:遵循插件开发规范,确保兼容性和稳定性
  5. 错误处理:完善的错误处理和恢复机制

通过这些高级技巧和最佳实践,可以构建出更加健壮、高效和可维护的 Vue 3 应用状态管理系统。 5

本站由 提供部署服务