
Pinia 作为 Vue3 官方推荐的状态管理方案,虽以轻量、简洁的特性广受青睐,但原生不具备状态持久化能力—— 页面刷新后,Store 中的 state 会直接重置为初始值,这对于用户登录状态、全局配置、表单草稿等需要跨会话保留的数据而言,是无法忽视的痛点。
pinia-plugin-persistedstate 插件则完美填补了这一空白:它能以极低的接入成本实现 Pinia 状态的本地持久化存储(支持 localStorage/sessionStorage 等),且适配模块化 Store、支持自定义持久化规则。但在实战中,开发者常因配置不当踩坑:模块化场景下持久化范围混乱、敏感数据未做加密处理、不同存储介质选型错误,最终导致持久化失效或数据安全风险。
本文聚焦 pinia-plugin-persistedstate 插件实战解析,从核心配置、模块化适配、高级定制用法到高频避坑要点,全方位拆解状态持久化的实现逻辑与最佳实践,帮你彻底解决 Pinia 状态刷新丢失的问题,保障数据跨会话稳定留存。
一、核心原理和前置准备
1、核心原理
2、前置准备
(1)安装插件
# npm(最常用) npm install pinia-plugin-persistedstate --save # yarn yarn add pinia-plugin-persistedstate # pnpm(推荐,速度快) pnpm add pinia-plugin-persistedstate # 如需指定版本(避免版本兼容问题) npm install pinia-plugin-persistedstate@3.2.0 --save
(2)全局注册插件
// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 导入 Pinia 创建方法
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 导入插件
import App from './App.vue'
import router from './router' // 可选,项目路由
// 步骤1:创建 Pinia 实例(必须先创建实例,再注册插件)
const pinia = createPinia()
// 步骤2:注册持久化插件(核心)
pinia.use(piniaPluginPersistedstate)
// 步骤3:创建 Vue 实例并挂载(顺序不能乱)
const app = createApp(App)
app.use(router) // 挂载路由
app.use(pinia) // 挂载 Pinia(此时插件已注册)
app.mount('#app') // 挂载到 DOM二、基础使用(单Store 持久化)
1、极简配置(快速上手)
// src/stores/user.js
import { defineStore } from 'pinia'
// 定义用户 Store(ID 必须唯一:user)
export const useUserStore = defineStore('user', {
// 状态:返回函数(避免跨实例污染)
state: () => ({
token: '', // 用户令牌(核心,需持久化)
username: '', // 用户名
avatar: '', // 头像地址
isLogin: false, // 登录状态
loginTime: null, // 登录时间(Date 类型)
permissions: [] // 权限列表
}),
// 同步/异步 action(修改 state 的方法)
actions: {
// 登录:修改 state,插件会自动同步到本地存储
login(userInfo) {
this.token = userInfo.token
this.username = userInfo.username
this.avatar = userInfo.avatar
this.isLogin = true
this.loginTime = new Date() // Date 类型
this.permissions = userInfo.permissions
},
// 登出:重置 state,插件会自动清空本地存储
logout() {
this.$reset() // 重置为初始状态
}
},
// 开启持久化(默认规则)
persist: true
})配置项 | 默认值 | 说明 |
存储 key | Store ID(如:user) | 本地存储的键名:localStorage.getItem ('user') |
存储方式 | localStorage | 永久存储(关闭浏览器也不会丢) |
持久化字段 | 整个 state | 所有 state 字段都会被存储 |
序列化方式 | JSON.stringify/parse | 仅支持可序列化数据(字符串、数字、数组、普通对象) |
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 模拟登录
const mockLogin = () => {
userStore.login({
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
username: '张三',
avatar: '/avatar.png',
permissions: ['view', 'edit']
})
}
</script>
<template>
<button @click="mockLogin">模拟登录</button>
</template>模拟登录:点击按钮后,打开浏览器开发者工具(F12)→ Application → Local Storage → 查看是否有 user 键,值为序列化后的 state:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"username": "张三",
"avatar": "/avatar.png",
"isLogin": true,
"loginTime": "2026-03-22T10:00:00.000Z", // Date 被序列化为字符串
"permissions": ["view", "edit"]
}2、自定义配置(生产环境推荐,精细控制)
// src/stores/user.js
export const useUserStore = defineStore('user', {
state: () => ({ /* 同上 */ }),
actions: { /* 同上 */ },
// 自定义持久化配置(核心)
persist: {
// 1. 自定义存储 key(避免与其他项目/插件冲突)
key: 'my-project-user-store', // 本地存储键名:localStorage.getItem('my-project-user-store')
// 2. 选择存储方式(二选一)
storage: sessionStorage, // 会话存储(关闭标签页即丢失),默认 localStorage
// 3. 白名单:只持久化指定字段(推荐!减少存储体积)
paths: ['token', 'username', 'isLogin', 'permissions'], // 不存储 avatar、loginTime
// 4. 序列化/反序列化(处理特殊类型,如 Date、正则)
serializer: {
// 存储时:将 state 转为字符串(自定义逻辑)
serialize: (value) => {
// 示例:将 Date 类型转为时间戳,避免序列化后变成字符串
const serialized = { ...value }
if (serialized.loginTime) {
serialized.loginTime = new Date(serialized.loginTime).getTime()
}
return JSON.stringify(serialized)
},
// 读取时:将字符串转回 state(自定义逻辑)
deserialize: (value) => {
const parsed = JSON.parse(value)
// 示例:将时间戳转回 Date 类型
if (parsed.loginTime) {
parsed.loginTime = new Date(parsed.loginTime)
}
return parsed
}
},
// 5. 覆盖规则(高级:合并本地存储与初始 state)
merge: (persistedState, currentState) => {
// persistedState:本地存储的状态
// currentState:Store 的初始状态
// 自定义合并逻辑(默认是 Object.assign(currentState, persistedState))
return {
...currentState, // 保留初始状态的默认值
...persistedState, // 覆盖为本地存储的状态
// 特殊处理:权限列表合并(而非覆盖)
permissions: [...currentState.permissions, ...persistedState.permissions]
}
}
}
})配置项 | 类型 | 说明 |
key | string | 本地存储的键名,建议加项目前缀(如my-project-xxx),避免冲突 |
storage | Storage 接口 | 支持localStorage/sessionStorage,或自定义存储(如 cookie,下文讲) |
paths | string[] | 只持久化指定字段,格式为“字段名”(嵌套字段用点语法,如 user.info.name) |
serializer | {serialize, deserialize} | 自定义序列化 / 反序列化逻辑,处理 Date、正则等无法默认序列化的类型 |
merge | function | 自定义本地存储状态与初始 state 的合并规则,默认是 “覆盖” |
1、模块化目录结构(规范)
src/ ├── stores/ │ ├── user.js # 用户模块(持久化 token、登录状态) │ ├── cart.js # 购物车模块(持久化商品列表) │ ├── settings.js # 系统设置模块(持久化主题、字号) │ └── index.js # (可选)统一导出所有 Store └── main.js
2、多Store 配置示例
(1)购物车模块(cart.js)
// src/stores/cart.js
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
cartList: [], // 购物车商品列表
totalPrice: 0, // 总价
selectedIds: [] // 选中的商品 ID
}),
actions: {
addGoods(goods) {
this.cartList.push(goods)
this.calcTotalPrice()
},
calcTotalPrice() {
this.totalPrice = this.cartList.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
},
// 购物车持久化配置(只存商品列表,不存总价/选中 ID)
persist: {
key: 'my-project-cart-store',
storage: localStorage,
paths: ['cartList'] // 总价可通过 cartList 计算,无需持久化
}
})(2)系统设置模块(settings.js)
// src/stores/settings.js
import { defineStore } from 'pinia'
export const useSettingsStore = defineStore('settings', {
state: () => ({
theme: 'light', // 主题:light/dark
fontSize: 14, // 字号
language: 'zh-CN' // 语言
}),
actions: {
changeTheme(theme) {
this.theme = theme
}
},
// 系统设置持久化(全量存储,体积小)
persist: {
key: 'my-project-settings-store',
storage: localStorage
}
})3、组件中使用(无感知,与普通Store 一致)
<script setup>
import { storeToRefs } from 'pinia' // 解构保留响应式
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'
import { useSettingsStore } from '@/stores/settings'
// 实例化多个
Storeconst userStore = useUserStore()
const cartStore = useCartStore()
const settingsStore = useSettingsStore()
// 解构响应式状态(必须用 storeToRefs)
const { username, isLogin } = storeToRefs(userStore)
const { cartList } = storeToRefs(cartStore)
const { theme } = storeToRefs(settingsStore)
// 模拟添加商品到购物车(会自动持久化到本地存储)
const addToCart = () => {
cartStore.addGoods({ id: 1, name: 'Vue 实战教程', price: 99, quantity: 1 })
}
// 切换主题(会自动持久化)
const toggleTheme = () => {
settingsStore.changeTheme(theme.value === 'light' ? 'dark' : 'light')
}
</script>
<template>
<div :class="`theme-${theme}`">
<div v-if="isLogin">欢迎 {{ username }}</div>
<div>购物车:{{ cartList.length }} 件商品</div>
<button @click="addToCart">添加商品</button>
<button @click="toggleTheme">切换主题</button>
</div>
</template>四、高级用法(自定义存储介质/ 全局配置)
1、自定义存储介质(如 Cookie)
(1)封装 Cookie 操作工具(推荐用 js-cookie 库)
# 安装 js-cookie npm install js-cookie --save
// src/utils/cookieStorage.js
import Cookies from 'js-cookie'
// 实现 Storage 接口(与 localStorage 一致)
export const cookieStorage = {
// 设置 Cookie
setItem(key, value) {
Cookies.set(key, value, {
expires: 7, // 有效期 7 天
path: '/', // 全局生效
secure: process.env.NODE_ENV === 'production' // 生产环境启用 HTTPS
})
},
// 获取 Cookie
getItem(key) {
return Cookies.get(key)
},
// 删除 Cookie
removeItem(key) {
Cookies.remove(key, { path: '/' })
},
// 清空所有 Cookie(可选)
clear() {
Object.keys(Cookies.get()).forEach(key => {
Cookies.remove(key, { path: '/' })
})
}
}(2)在 Store 中使用 Cookie 存储
// src/stores/user.js
import { cookieStorage } from '@/utils/cookieStorage'
export const useUserStore = defineStore('user', {
state: () => ({ /* 同上 */ }),
actions: { /* 同上 */ },
persist: {
key: 'my-project-user-cookie',
storage: cookieStorage, // 使用自定义 Cookie 存储
paths: ['token', 'isLogin']
}
})2、全局配置(统一所有 Store 的默认规则)
// src/main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
// 注册插件时全局配置
pinia.use(
piniaPluginPersistedstate({
// 全局默认存储 key 前缀
key: (id) => `my-project-${id}`, // 如 Store ID 为 user → key 为 my-project-user
// 全局默认存储方式
storage: localStorage,
// 全局默认序列化逻辑
serializer: {
serialize: (value) => JSON.stringify(value),
deserialize: (value) => JSON.parse(value)
}
})
)// 全局配置 key 前缀为 my-project-
// 局部配置 key 为 custom-user-key → 最终 key 为 custom-user-key(覆盖全局)
persist: {
key: 'custom-user-key'
}五、常见问题排查(避坑+ 解决方案)
1、刷新页面后状态未恢复
插件未正确注册(顺序错误); persist 配置为 false 或未配置; paths 未包含需要恢复的字段; 存储key 冲突或被覆盖; 本地存储被清空(如浏览器清理缓存); 序列化/ 反序列化失败(如状态包含不可序列化数据)。
检查注册顺序:createPinia() → pinia.use(插件) → app.use(pinia); 确认Store 内 persist 配置正确(不是 false); 检查paths 是否包含目标字段(如需要恢复 token,则 paths 必须有 token); 打开浏览器开发者工具→ Application → 查看对应存储(localStorage/sessionStorage/cookie)是否有对应 key; 移除不可序列化数据(如函数、Symbol、循环引用对象),或自定义 serializer 处理;
// 在 Store 中打印调试
export const useUserStore = defineStore('user', {
state: () => ({ /* 同上 */ }),
actions: { /* 同上 */ },
persist: { /* 自定义配置 */ },
// 调试:Store 初始化后打印本地存储数据
init() {
const persisted = localStorage.getItem('my-project-user-store')
console.log('本地存储数据:', persisted)
console.log('解析后:', JSON.parse(persisted || '{}'))
}
})
// 组件中调用 init 调试
const userStore = useUserStore()
userStore.init()2、解构 Store 后状态不更新(响应式丢失)
const { token, isLogin } = useUserStore() // 直接解构,丢失响应式
token.value = 'new-token' // 页面不刷新正确代码:
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
const { token, isLogin } = storeToRefs(userStore) // 保留响应式
// 或直接通过实例修改(推荐)
userStore.token = 'new-token'3、Date / 正则等类型序列化后丢失类型
persist: {
serializer: {
serialize: (value) => {
const serialized = { ...value }
if (serialized.loginTime) {
serialized.loginTime = serialized.loginTime.getTime() // 转时间戳
}
return JSON.stringify(serialized)
},
deserialize: (value) => {
const parsed = JSON.parse(value)
if (parsed.loginTime) {
parsed.loginTime = new Date(parsed.loginTime) // 转回 Date
}
return parsed
}
}
}4、手动修改本地存储后状态不一致
// 组件中直接修改 localStorage
localStorage.setItem('my-project-user-store', '{"token":"fake-token"}')// 正确方式1:调用 action
userStore.login({ token: 'real-token', username: '李四' })
// 正确方式2:使用 $patch 批量修改
userStore.$patch({
token: 'real-token',
isLogin: true
})5、多个 Store 共用同一个 key 导致状态覆盖
// 用户模块
persist: { key: 'my-project-user-store' }
// 购物车模块
persist: { key: 'my-project-cart-store' }










