Pinia 状态持久化 #
概述 #
状态持久化是将 Store 的状态保存到本地存储(如 localStorage、sessionStorage)的技术,这样在页面刷新或重新打开应用时,状态不会丢失。
使用 pinia-plugin-persistedstate #
安装 #
bash
npm install pinia-plugin-persistedstate
基本配置 #
ts
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
const app = createApp(App)
app.use(pinia)
启用持久化 #
ts
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: '',
preferences: {
theme: 'light',
language: 'zh-CN'
}
}),
// 启用持久化
persist: true
})
选择性持久化 #
ts
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: '',
temporaryData: ''
}),
persist: {
// 只持久化指定字段
paths: ['name', 'token']
}
})
自定义存储 #
ts
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: ''
}),
persist: {
// 使用 sessionStorage
storage: sessionStorage
}
})
自定义序列化 #
ts
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
createdAt: null as Date | null
}),
persist: {
serializer: {
serialize: (state) => JSON.stringify(state),
deserialize: (value) => {
const state = JSON.parse(value)
if (state.createdAt) {
state.createdAt = new Date(state.createdAt)
}
return state
}
}
}
})
手动实现持久化 #
基本实现 #
ts
// stores/user.ts
import { defineStore } from 'pinia'
const STORAGE_KEY = 'user-store'
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: '',
preferences: {
theme: 'light'
}
}),
actions: {
// 从存储加载
loadFromStorage() {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved) {
this.$patch(JSON.parse(saved))
}
},
// 保存到存储
saveToStorage() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.$state))
},
// 清除存储
clearStorage() {
localStorage.removeItem(STORAGE_KEY)
}
}
})
使用插件实现 #
ts
// plugins/persist.ts
import type { PiniaPluginContext } from 'pinia'
interface PersistOptions {
key?: string
storage?: Storage
paths?: string[]
}
export function persistPlugin({ store, options }: PiniaPluginContext) {
const persistOptions = options.persist as PersistOptions | boolean
if (!persistOptions) return
const config: PersistOptions = typeof persistOptions === 'boolean'
? {}
: persistOptions
const {
key = store.$id,
storage = localStorage,
paths
} = config
// 从存储恢复状态
const savedState = storage.getItem(key)
if (savedState) {
const parsed = JSON.parse(savedState)
if (paths) {
paths.forEach(path => {
if (parsed[path] !== undefined) {
store.$patch({ [path]: parsed[path] })
}
})
} else {
store.$patch(parsed)
}
}
// 监听状态变化并保存
store.$subscribe((mutation, state) => {
const stateToPersist = paths
? paths.reduce((acc, path) => {
acc[path] = state[path]
return acc
}, {} as Record<string, any>)
: state
storage.setItem(key, JSON.stringify(stateToPersist))
})
}
ts
// main.ts
import { persistPlugin } from './plugins/persist'
const pinia = createPinia()
pinia.use(persistPlugin)
使用 Store #
ts
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: ''
}),
persist: {
key: 'my-app-user',
storage: localStorage,
paths: ['name', 'token']
}
})
高级持久化方案 #
加密存储 #
ts
// utils/encryptedStorage.ts
import CryptoJS from 'crypto-js'
const SECRET_KEY = 'your-secret-key'
export const encryptedStorage = {
getItem(key: string): string | null {
const encrypted = localStorage.getItem(key)
if (!encrypted) return null
try {
const decrypted = CryptoJS.AES.decrypt(encrypted, SECRET_KEY)
return decrypted.toString(CryptoJS.enc.Utf8)
} catch {
return null
}
},
setItem(key: string, value: string): void {
const encrypted = CryptoJS.AES.encrypt(value, SECRET_KEY).toString()
localStorage.setItem(key, encrypted)
},
removeItem(key: string): void {
localStorage.removeItem(key)
}
}
ts
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({
sensitiveData: ''
}),
persist: {
storage: encryptedStorage
}
})
IndexedDB 存储 #
ts
// utils/idbStorage.ts
import { openDB, DBSchema, IDBPDatabase } from 'idb'
interface PiniaDB extends DBSchema {
store: {
key: string
value: any
}
}
class IDBStorage {
private db: Promise<IDBPDatabase<PiniaDB>>
constructor() {
this.db = openDB<PiniaDB>('pinia-storage', 1, {
upgrade(db) {
db.createObjectStore('store')
}
})
}
async getItem(key: string): Promise<string | null> {
const db = await this.db
const value = await db.get('store', key)
return value ?? null
}
async setItem(key: string, value: string): Promise<void> {
const db = await this.db
await db.put('store', value, key)
}
async removeItem(key: string): Promise<void> {
const db = await this.db
await db.delete('store', key)
}
}
export const idbStorage = new IDBStorage()
ts
// stores/largeData.ts
export const useLargeDataStore = defineStore('largeData', {
state: () => ({
items: []
}),
persist: {
storage: idbStorage as any
}
})
过期时间 #
ts
// plugins/persistWithExpiry.ts
import type { PiniaPluginContext } from 'pinia'
interface ExpiryOptions {
key?: string
storage?: Storage
expiry?: number // 过期时间(毫秒)
}
export function persistWithExpiryPlugin({ store, options }: PiniaPluginContext) {
const config = options.persistWithExpiry as ExpiryOptions | undefined
if (!config) return
const {
key = store.$id,
storage = localStorage,
expiry = 24 * 60 * 60 * 1000 // 默认 24 小时
} = config
// 恢复状态
const saved = storage.getItem(key)
if (saved) {
try {
const { state, timestamp } = JSON.parse(saved)
const now = Date.now()
// 检查是否过期
if (now - timestamp < expiry) {
store.$patch(state)
} else {
storage.removeItem(key)
}
} catch {
storage.removeItem(key)
}
}
// 保存状态
store.$subscribe((mutation, state) => {
storage.setItem(key, JSON.stringify({
state,
timestamp: Date.now()
}))
})
}
多标签页同步 #
ts
// plugins/syncAcrossTabs.ts
import type { PiniaPluginContext } from 'pinia'
export function syncAcrossTabsPlugin({ store, options }: PiniaPluginContext) {
const config = options.syncTabs
if (!config) return
const key = typeof config === 'string' ? config : store.$id
// 监听其他标签页的变化
window.addEventListener('storage', (event) => {
if (event.key === key && event.newValue) {
try {
const newState = JSON.parse(event.newValue)
store.$patch(newState)
} catch (e) {
console.error('Failed to sync state:', e)
}
}
})
}
ts
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
token: ''
}),
persist: true,
syncTabs: 'user-store' // 启用跨标签页同步
})
最佳实践 #
1. 选择性持久化 #
ts
// 只持久化必要的数据
persist: {
paths: ['token', 'userId', 'preferences']
// 不持久化临时数据、UI 状态等
}
2. 敏感数据处理 #
ts
// 不要持久化敏感数据
persist: {
paths: ['name', 'preferences']
// 密码、密钥等不应持久化
}
// 或者使用加密
persist: {
storage: encryptedStorage
}
3. 版本控制 #
ts
// stores/user.ts
const VERSION = 1
export const useUserStore = defineStore('user', {
state: () => ({
_version: VERSION,
name: '',
token: ''
}),
persist: {
serializer: {
serialize: JSON.stringify,
deserialize: (value) => {
const state = JSON.parse(value)
// 版本迁移
if (state._version !== VERSION) {
// 执行迁移逻辑
state._version = VERSION
}
return state
}
}
}
})
4. 错误处理 #
ts
persist: {
serializer: {
serialize: (state) => {
try {
return JSON.stringify(state)
} catch (e) {
console.error('Failed to serialize state:', e)
return '{}'
}
},
deserialize: (value) => {
try {
return JSON.parse(value)
} catch (e) {
console.error('Failed to deserialize state:', e)
return {}
}
}
}
}
下一步 #
现在你已经掌握了状态持久化,接下来让我们学习热更新。
- 热更新 - 开发时热替换
最后更新:2026-03-28