一、原因分析
1. 状态存储机制
Pinia 的状态存储在内存中。当你刷新页面时,浏览器会重新加载页面,导致 JavaScript 环境重新初始化,这意味着存储在内存中的数据会丢失。因为刷新操作会重新加载整个应用程序,包括重新创建 Vue 实例和 Pinia 实例,而原来存储在 Pinia store 中的数据是存储在之前的 JavaScript 运行时环境中的,一旦刷新,之前的环境被清除,数据也就消失了。
2. 没有持久化存储
Pinia 本身并不会自动将数据持久化到本地存储(如 localStorage 或 sessionStorage)或其他持久化存储介质中。如果没有使用持久化存储,那么存储在 Pinia 中的数据就只是临时存储在内存中,仅在当前页面会话期间有效。
二、解决办法
可以使用 pinia-plugin-persist 插件
1. 安装
npm install pinia-plugin-persist -D
2. 引入
在store/index.ts
文件中
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'
const store = createPinia()
store.use(piniaPersist)
3. 手动配置持久化
import { defineStore } from 'pinia';
const useMyStore = defineStore('myStore', {
state: () => ({
count: 0
}),
persist: {
enabled: true
// 其他配置可以去pinia-plugin-persist文档查看,https://seb-l.github.io/pinia-plugin-persist/advanced/strategies.html
}
});
三、源码解析
核心文件 index.ts
import { PiniaPluginContext } from 'pinia'
export interface PersistStrategy {
key?: string;
storage?: Storage;
paths?: string[];
}
export interface PersistOptions {
enabled: true;
strategies?: PersistStrategy[];
}
type Store = PiniaPluginContext['store'];
type PartialState = Partial<Store['$state']>;
declare module 'pinia' {
export interface DefineStoreOptionsBase<S, Store> {
persist?: PersistOptions;
}
}
export const updateStorage = (strategy: PersistStrategy, store: Store) => {
const storage = strategy.storage || sessionStorage
const storeKey = strategy.key || store.$id
if (strategy.paths) {
const partialState = strategy.paths.reduce((finalObj, key) => {
finalObj[key] = store.$state[key]
return finalObj
}, {} as PartialState)
storage.setItem(storeKey, JSON.stringify(partialState))
} else {
storage.setItem(storeKey, JSON.stringify(store.$state))
}
}
export default ({ options, store }: PiniaPluginContext): void => {
if (options.persist?.enabled) {
const defaultStrat: PersistStrategy[] = [{
key: store.$id,
storage: sessionStorage,
}]
const strategies = options.persist?.strategies?.length ? options.persist?.strategies : defaultStrat
strategies.forEach((strategy) => {
const storage = strategy.storage || sessionStorage
const storeKey = strategy.key || store.$id
const storageResult = storage.getItem(storeKey)
if (storageResult) {
store.$patch(JSON.parse(storageResult))
updateStorage(strategy, store)
}
})
store.$subscribe(() => {
strategies.forEach((strategy) => {
updateStorage(strategy, store)
})
})
}
}
1. 导入和类型定义
import { PiniaPluginContext } from 'pinia'
export interface PersistStrategy {
key?: string;
storage?: Storage;
paths?: string[];
}
export interface PersistOptions {
enabled: true;
strategies?: PersistStrategy[];
}
type Store = PiniaPluginContext['store'];
type PartialState = Partial<Store['$state']>;
- 导入
PiniaPluginContext
:从pinia
库中导入PiniaPluginContext
,它包含了 Pinia 插件执行时所需的上下文信息,如store
、options
等。 PersistStrategy
接口:定义了持久化策略的类型。key
:可选属性,用于指定存储在Storage
中的键名,默认为store
的$id
。storage
:可选属性,指定存储的位置,类型为Storage
,可以是localStorage
或sessionStorage
等,默认为sessionStorage
。paths
:可选属性,是一个字符串数组,用于指定只持久化store
状态中的部分属性。
PersistOptions
接口:定义了持久化的配置选项。enabled
:必须为true
,表示开启持久化功能。strategies
:可选属性,是一个PersistStrategy
数组,可指定多个持久化策略。
- 类型别名:
Store
:从PiniaPluginContext
中提取store
的类型。PartialState
:表示store
状态的部分属性,是Store['$state']
的Partial
类型。
2. 扩展 Pinia 选项接口
declare module 'pinia' {
export interface DefineStoreOptionsBase<S, Store> {
persist?: PersistOptions;
}
}
通过 declare module
语法扩展 pinia
库中的 DefineStoreOptionsBase
接口,添加了一个可选的 persist
属性,类型为 PersistOptions
。这样,在定义 Pinia store 时,就可以使用 persist
选项来配置持久化功能。
3. updateStorage
函数
export const updateStorage = (strategy: PersistStrategy, store: Store) => {
const storage = strategy.storage || sessionStorage
const storeKey = strategy.key || store.$id
if (strategy.paths) {
const partialState = strategy.paths.reduce((finalObj, key) => {
finalObj[key] = store.$state[key]
return finalObj
}, {} as PartialState)
storage.setItem(storeKey, JSON.stringify(partialState))
} else {
storage.setItem(storeKey, JSON.stringify(store.$state))
}
}
- 功能:该函数用于将
store
的状态存储到指定的Storage
中。 - 参数:
strategy
:持久化策略对象,包含存储的键名、存储位置和要持久化的属性路径等信息。store
:Pinia store 实例。
- 逻辑:
- 首先,根据
strategy
中的storage
属性获取存储位置,若未指定则使用sessionStorage
;根据strategy
中的key
属性获取存储键名,若未指定则使用store
的$id
。 - 若
strategy
中指定了paths
,则只提取store
状态中paths
所包含的属性,将其存储到partialState
对象中,然后将partialState
序列化为 JSON 字符串并存储到Storage
中。 - 若未指定
paths
,则将整个store
状态序列化为 JSON 字符串并存储到Storage
中。
- 首先,根据
4. 插件主函数
export default ({ options, store }: PiniaPluginContext): void => {
if (options.persist?.enabled) {
const defaultStrat: PersistStrategy[] = [{
key: store.$id,
storage: sessionStorage,
}]
const strategies = options.persist?.strategies?.length ? options.persist?.strategies : defaultStrat
strategies.forEach((strategy) => {
const storage = strategy.storage || sessionStorage
const storeKey = strategy.key || store.$id
const storageResult = storage.getItem(storeKey)
if (storageResult) {
store.$patch(JSON.parse(storageResult))
updateStorage(strategy, store)
}
})
store.$subscribe(() => {
strategies.forEach((strategy) => {
updateStorage(strategy, store)
})
})
}
}
- 功能:这是 Pinia 插件的主函数,用于实现持久化功能。
- 参数:接收一个
PiniaPluginContext
对象,包含options
(store 的配置选项)和store
(Pinia store 实例)。 - 逻辑:
- 首先检查
options.persist
是否存在且enabled
为true
,若不满足则不执行持久化操作。 - 定义一个默认的持久化策略
defaultStrat
,使用store
的$id
作为键名,sessionStorage
作为存储位置。 - 根据
options.persist.strategies
是否存在来确定最终使用的持久化策略数组strategies
,若不存在则使用默认策略。 - 遍历
strategies
数组,对于每个策略:- 从
Storage
中获取存储的状态信息。 - 若获取到存储结果,则使用
store.$patch
方法将存储的状态应用到store
中,并调用updateStorage
函数更新存储。
- 从
- 使用
store.$subscribe
方法监听store
的状态变化,当状态发生变化时,遍历strategies
数组,调用updateStorage
函数将最新状态存储到Storage
中。
- 首先检查
总结
这段代码实现了一个 Pinia 插件,用于将 Pinia store 的状态持久化到 Storage
中。它支持多种持久化策略,可以指定存储的键名、存储位置和要持久化的属性路径。在应用启动时,会尝试从 Storage
中恢复状态;在状态发生变化时,会自动更新存储。