状态持久化 #
什么是状态持久化? #
状态持久化是指将应用状态保存到本地存储(如 localStorage、sessionStorage),在页面刷新或重新打开应用时能够恢复状态。
为什么需要持久化? #
text
持久化应用场景
├── 用户偏好 ──── 主题、语言、布局设置
├── 表单数据 ──── 防止意外丢失
├── 登录状态 ──── 免重复登录
├── 购物车 ────── 保留购物信息
└── 缓存数据 ──── 减少网络请求
基本使用 #
最简单的持久化 #
tsx
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useStore = create(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'counter-storage',
}
)
)
完整类型定义 #
tsx
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface State {
count: number
name: string
increment: () => void
setName: (name: string) => void
}
const useStore = create<State>()(
persist(
(set) => ({
count: 0,
name: '',
increment: () => set((state) => ({ count: state.count + 1 })),
setName: (name) => set({ name }),
}),
{
name: 'my-storage',
storage: createJSONStorage(() => localStorage),
}
)
)
存储引擎 #
localStorage #
数据永久保存,除非手动清除:
tsx
import { persist, createJSONStorage } from 'zustand/middleware'
persist(
(set) => ({ /* store */ }),
{
name: 'my-storage',
storage: createJSONStorage(() => localStorage),
}
)
sessionStorage #
数据在标签页关闭后清除:
tsx
persist(
(set) => ({ /* store */ }),
{
name: 'my-storage',
storage: createJSONStorage(() => sessionStorage),
}
)
自定义存储引擎 #
IndexedDB 存储 #
tsx
const indexedDBStorage = {
getItem: async (name: string): Promise<string | null> => {
return new Promise((resolve) => {
const request = indexedDB.open('zustand-store', 1)
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result
if (!db.objectStoreNames.contains('stores')) {
db.createObjectStore('stores')
}
}
request.onsuccess = (event) => {
const db = (event.target as IDBOpenDBRequest).result
const transaction = db.transaction('stores', 'readonly')
const store = transaction.objectStore('stores')
const getRequest = store.get(name)
getRequest.onsuccess = () => {
resolve(getRequest.result || null)
}
getRequest.onerror = () => {
resolve(null)
}
}
request.onerror = () => {
resolve(null)
}
})
},
setItem: async (name: string, value: string): Promise<void> => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('zustand-store', 1)
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result
if (!db.objectStoreNames.contains('stores')) {
db.createObjectStore('stores')
}
}
request.onsuccess = (event) => {
const db = (event.target as IDBOpenDBRequest).result
const transaction = db.transaction('stores', 'readwrite')
const store = transaction.objectStore('stores')
store.put(value, name)
transaction.oncomplete = () => resolve()
transaction.onerror = () => reject(transaction.error)
}
request.onerror = () => {
reject(request.error)
}
})
},
removeItem: async (name: string): Promise<void> => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('zustand-store', 1)
request.onsuccess = (event) => {
const db = (event.target as IDBOpenDBRequest).result
const transaction = db.transaction('stores', 'readwrite')
const store = transaction.objectStore('stores')
store.delete(name)
transaction.oncomplete = () => resolve()
transaction.onerror = () => reject(transaction.error)
}
})
},
}
// 使用
persist(
(set) => ({ /* store */ }),
{
name: 'my-storage',
storage: indexedDBStorage,
}
)
加密存储 #
tsx
import CryptoJS from 'crypto-js'
const SECRET_KEY = 'your-secret-key'
const encryptedStorage = {
getItem: (name: string): string | null => {
const encrypted = localStorage.getItem(name)
if (!encrypted) return null
try {
const decrypted = CryptoJS.AES.decrypt(encrypted, SECRET_KEY)
return decrypted.toString(CryptoJS.enc.Utf8)
} catch {
return null
}
},
setItem: (name: string, value: string): void => {
const encrypted = CryptoJS.AES.encrypt(value, SECRET_KEY).toString()
localStorage.setItem(name, encrypted)
},
removeItem: (name: string): void => {
localStorage.removeItem(name)
},
}
// 使用
persist(
(set) => ({ /* store */ }),
{
name: 'secure-storage',
storage: encryptedStorage,
}
)
部分持久化 #
partialize 选项 #
只持久化需要的状态:
tsx
interface State {
// 需要持久化
user: User | null
theme: 'light' | 'dark'
// 不需要持久化
isLoading: boolean
error: string | null
// Actions
setUser: (user: User) => void
setTheme: (theme: 'light' | 'dark') => void
}
const useStore = create<State>()(
persist(
(set) => ({
user: null,
theme: 'light',
isLoading: false,
error: null,
setUser: (user) => set({ user }),
setTheme: (theme) => set({ theme }),
}),
{
name: 'user-preferences',
partialize: (state) => ({
user: state.user,
theme: state.theme,
}),
}
)
)
使用函数过滤 #
tsx
persist(
(set) => ({
count: 0,
name: '',
temporaryData: null,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'my-storage',
partialize: (state) => {
const { temporaryData, ...persisted } = state
return persisted
},
}
)
使用数组指定字段 #
tsx
persist(
(set) => ({
count: 0,
name: '',
temp: '',
}),
{
name: 'my-storage',
partialize: ['count', 'name'] as const,
}
)
数据迁移 #
版本控制 #
tsx
const useStore = create(
persist(
(set) => ({
count: 0,
version: 2, // 当前版本
}),
{
name: 'counter-storage',
version: 2, // 存储版本
}
)
)
migrate 函数 #
处理版本升级时的数据迁移:
tsx
interface State {
user: {
name: string
email: string
avatar?: string // v2 新增
}
preferences: {
theme: 'light' | 'dark'
language: string
}
}
const useStore = create<State>()(
persist(
(set) => ({
user: {
name: '',
email: '',
avatar: '',
},
preferences: {
theme: 'light',
language: 'zh-CN',
},
}),
{
name: 'user-storage',
version: 2,
migrate: (persisted: any, version: number) => {
// v0 -> v1: 添加 preferences
if (version < 1) {
persisted.preferences = {
theme: 'light',
language: 'zh-CN',
}
}
// v1 -> v2: 添加 avatar 字段
if (version < 2) {
persisted.user.avatar = ''
}
return persisted
},
}
)
)
复杂迁移示例 #
tsx
interface UserV1 {
username: string
email: string
}
interface UserV2 {
profile: {
name: string
email: string
avatar: string
}
settings: {
theme: 'light' | 'dark'
notifications: boolean
}
}
const useUserStore = create<UserV2>()(
persist(
(set) => ({
profile: {
name: '',
email: '',
avatar: '',
},
settings: {
theme: 'light',
notifications: true,
},
}),
{
name: 'user-storage',
version: 2,
migrate: (persisted: any, version: number) => {
if (version === 0) {
// v0: 扁平结构
return {
profile: {
name: persisted.username || '',
email: persisted.email || '',
avatar: '',
},
settings: {
theme: 'light',
notifications: true,
},
}
}
if (version === 1) {
// v1: 添加 settings
return {
...persisted,
settings: {
theme: 'light',
notifications: true,
},
}
}
return persisted
},
}
)
)
恢复状态 #
onRehydrateStorage #
在状态恢复后执行回调:
tsx
persist(
(set) => ({
user: null,
token: null,
isAuthenticated: false,
login: (user, token) => set({ user, token, isAuthenticated: true }),
logout: () => set({ user: null, token: null, isAuthenticated: false }),
}),
{
name: 'auth-storage',
onRehydrateStorage: () => (state) => {
console.log('状态已恢复:', state)
// 恢复后验证 token
if (state?.token) {
validateToken(state.token).then((valid) => {
if (!valid) {
state.logout()
}
})
}
},
}
)
手动触发恢复 #
tsx
const useStore = create(
persist(
(set) => ({
count: 0,
}),
{
name: 'counter-storage',
}
)
)
// 手动恢复
const rehydrate = async () => {
await useStore.persist.rehydrate()
}
清除持久化数据 #
tsx
// 清除特定 store 的持久化数据
useStore.persist.clearStorage()
// 或使用 API
const api = useStore.getState()
if ('persist' in api) {
(api as any).persist.clearStorage()
}
高级用法 #
条件持久化 #
tsx
const useStore = create(
persist(
(set) => ({
count: 0,
shouldPersist: true,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'conditional-storage',
// 根据条件决定是否持久化
partialize: (state) =>
state.shouldPersist ? { count: state.count } : {},
}
)
)
多存储实例 #
tsx
const createUserStore = (userId: string) =>
create(
persist(
(set) => ({
data: null,
setData: (data) => set({ data }),
}),
{
name: `user-${userId}-storage`,
}
)
)
// 使用
const useUser1Store = createUserStore('user-1')
const useUser2Store = createUserStore('user-2')
SSR 兼容 #
tsx
const useStore = create(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'ssr-storage',
storage: createJSONStorage(() => {
// SSR 时返回空存储
if (typeof window === 'undefined') {
return {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
}
}
return localStorage
}),
}
)
)
实际案例 #
用户偏好设置 #
tsx
interface Preferences {
theme: 'light' | 'dark' | 'system'
language: string
fontSize: 'small' | 'medium' | 'large'
sidebarCollapsed: boolean
notifications: {
email: boolean
push: boolean
sound: boolean
}
}
interface PreferencesState extends Preferences {
setTheme: (theme: Preferences['theme']) => void
setLanguage: (language: string) => void
setFontSize: (size: Preferences['fontSize']) => void
toggleSidebar: () => void
updateNotifications: (notifications: Partial<Preferences['notifications']>) => void
reset: () => void
}
const defaultPreferences: Preferences = {
theme: 'system',
language: 'zh-CN',
fontSize: 'medium',
sidebarCollapsed: false,
notifications: {
email: true,
push: true,
sound: false,
},
}
const usePreferencesStore = create<PreferencesState>()(
persist(
(set) => ({
...defaultPreferences,
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
setFontSize: (fontSize) => set({ fontSize }),
toggleSidebar: () => set((state) => ({
sidebarCollapsed: !state.sidebarCollapsed
})),
updateNotifications: (notifications) => set((state) => ({
notifications: { ...state.notifications, ...notifications },
})),
reset: () => set(defaultPreferences),
}),
{
name: 'user-preferences',
version: 1,
migrate: (persisted: any, version: number) => {
if (version === 0) {
return {
...defaultPreferences,
...persisted,
notifications: {
...defaultPreferences.notifications,
...(persisted.notifications || {}),
},
}
}
return persisted
},
}
)
)
购物车持久化 #
tsx
interface CartItem {
productId: string
name: string
price: number
quantity: number
image: string
}
interface CartState {
items: CartItem[]
totalItems: number
totalPrice: number
addItem: (item: Omit<CartItem, 'quantity'>) => void
removeItem: (productId: string) => void
updateQuantity: (productId: string, quantity: number) => void
clearCart: () => void
}
const useCartStore = create<CartState>()(
persist(
(set, get) => ({
items: [],
totalItems: 0,
totalPrice: 0,
addItem: (item) => set((state) => {
const existingItem = state.items.find(i => i.productId === item.productId)
let newItems: CartItem[]
if (existingItem) {
newItems = state.items.map(i =>
i.productId === item.productId
? { ...i, quantity: i.quantity + 1 }
: i
)
} else {
newItems = [...state.items, { ...item, quantity: 1 }]
}
return {
items: newItems,
totalItems: newItems.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: newItems.reduce((sum, i) => sum + i.price * i.quantity, 0),
}
}),
removeItem: (productId) => set((state) => {
const newItems = state.items.filter(i => i.productId !== productId)
return {
items: newItems,
totalItems: newItems.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: newItems.reduce((sum, i) => sum + i.price * i.quantity, 0),
}
}),
updateQuantity: (productId, quantity) => set((state) => {
if (quantity <= 0) {
return get().removeItem(productId)
}
const newItems = state.items.map(i =>
i.productId === productId ? { ...i, quantity } : i
)
return {
items: newItems,
totalItems: newItems.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: newItems.reduce((sum, i) => sum + i.price * i.quantity, 0),
}
}),
clearCart: () => set({
items: [],
totalItems: 0,
totalPrice: 0,
}),
}),
{
name: 'shopping-cart',
partialize: (state) => ({
items: state.items,
}),
}
)
)
最佳实践 #
1. 不要持久化敏感数据 #
tsx
// ❌ 不好:持久化敏感数据
partialize: (state) => ({
password: state.password,
token: state.token,
})
// ✅ 好:排除敏感数据
partialize: (state) => {
const { password, token, ...safe } = state
return safe
}
2. 设置合理的版本号 #
tsx
// ✅ 好:使用版本号和迁移函数
{
name: 'my-storage',
version: 2,
migrate: (persisted, version) => {
// 迁移逻辑
},
}
3. 处理存储失败 #
tsx
const safeStorage = {
getItem: (name: string) => {
try {
return localStorage.getItem(name)
} catch {
return null
}
},
setItem: (name: string, value: string) => {
try {
localStorage.setItem(name, value)
} catch (error) {
console.error('存储失败:', error)
}
},
removeItem: (name: string) => {
try {
localStorage.removeItem(name)
} catch (error) {
console.error('删除失败:', error)
}
},
}
总结 #
状态持久化的关键点:
- 使用
persist中间件实现持久化 - 选择合适的存储引擎
- 使用
partialize只持久化需要的状态 - 使用
version和migrate处理版本升级 - 使用
onRehydrateStorage处理恢复后的逻辑
接下来,让我们学习 TypeScript 集成,掌握完整的类型支持。
最后更新:2026-03-28