107.vue插件机制piniaStoragePlugins
我们来看下面的代码

storage.js
export function piniaStoragePlugins({ store }) { const key = `pinia-${store.$id}` // 2️⃣ 初始化恢复:只恢复 count const saved = localStorage.getItem(key) if (saved) { // 注意:这里取出来的是数字,需要包进对象里给 $patch store.$patch({ count: JSON.parse(saved) }) } // 3️⃣ 监听变化:只存 count store.$subscribe((_, state) => { // 核心改动:只序列化 state.count localStorage.setItem(key, JSON.stringify(state.count)) }) store.$onAction(({ name, after }) => { after(() => { console.log(`[Action] ${name} completed`) }) }) }
在main.ts中如何使用插件了
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
// main.js
// 必须加上 .js 后缀
import { piniaStoragePlugins } from './plugins/storage.js'
// 创建 Vue 应用实例
const app = createApp(App)
// 创建 Pinia 实例
const pinia = createPinia()
pinia.use(piniaStoragePlugins)
// 使用 Pinia(必须在 mount 之前)
app.use(pinia)
// 挂载到页面
app.mount('#app')
hell.vue中的代码如下
<template>
<p>hello.vue</p>
<p>原始count: {{ store.count }}</p>
<p>hello22.vue</p>
<p>双倍count: {{ store.doubleCount }}</p>
<p>{{ store.getCount }}</p>
<!-- 操作按钮 -->
<div class="btn-group">
<button @click="store.count++">+1</button>
<button @click="store.addCount">+10</button>
<button @click="store.decrease">-5</button>
</div>
<h3>网络请求的结果如下所示</h3>
<ul v-if="store.data.length">
<li v-for="(item, index) in store.data" :key="index">
<h3>{{ item.title }}</h3>
<p>{{ item.content }}</p>
<img :src="item.img" :alt="item.title" style="width: 200px; margin: 10px 0;">
</li>
</ul>
<p v-else>加载中...</p>
</template>
<script setup>
import { onMounted } from 'vue'
import piniaCountStore from '../store/countstore'
const store = piniaCountStore()
onMounted(() => {
// 使用你提供的接口地址
store.getData('http://iwenwiki.com/api/blueberrypai/getIndexBanner.php')
})
</script>
<style scoped>
p {
font-size: 18px;
}
.btn-group {
margin: 15px 0;
}
button {
margin-right: 10px;
padding: 5px 15px;
}
li {
border-bottom: 1px solid #eee;
padding: 15px 0;
}
</style>
app.vue的代码如下
<template> <h1>app vue</h1> <hello /> </template> <script setup> // 引入子组件 import hello from './components/hello.vue' </script> <style scoped> h1 { color: #42b983; } </style>
我们来看下运行的效果

这是一段非常经典、非常地道的 Pinia 持久化插件代码。它实现了 “局部持久化”(只存 count,不管其他字段)。
我会按 “代码执行顺序” + “设计思想” 两个维度,给你拆解得明明白白。
一、整体在干什么?(一句话版)
这个插件会在 Store 创建时,从 localStorage 恢复
count;在count变化时,自动把最新的count存回 localStorage。
二、逐行详细解析
1️⃣ 函数定义与参数解构
export function piniaStoragePlugins({ store }) {
-
export function:这是一个命名导出的插件函数,方便在main.js中通过import { piniaStoragePlugins }引入。 -
{ store }:这是解构赋值。Pinia 在调用插件时,会传入一个 Context 对象。这里直接取出其中的store实例。 -
store:这就是你在组件中通过useCountStore()拿到的那个 Store 实例。插件在这里拿到了它的“控制权”。
2️⃣ 生成 localStorage 的 Key
const key = `pinia-${store.$id}`
-
store.$id:这是 Store 的唯一标识符。它来自于你定义 Store 时的第一个参数:defineStore('count', {...}) // 这里的 'count' 就是 $id -
拼接 Key:最终生成的 Key 是
pinia-count。 -
为什么这么做?
-
防冲突:如果你有多个 Store(user, cart, settings),它们不会互相覆盖。
-
可追溯:一眼就能看出这个 Key 属于哪个 Store。
-
3️⃣ 初始化恢复(核心逻辑)
const saved = localStorage.getItem(key)
if (saved) {
store.$patch({
count: JSON.parse(saved)
})
}
这是页面加载时执行的代码。
-
localStorage.getItem(key):尝试从浏览器本地存储中读取数据。 -
if (saved):判断是否存在缓存。-
如果是第一次访问,或者清除了缓存,
saved为null,直接跳过,使用 Store 定义的初始值count: 10。 -
如果之前存过,
saved是字符串(比如"15")。
-
-
JSON.parse(saved):把字符串转换回 JavaScript 数字(比如15)。 -
store.$patch({ count: 15 }):这是最关键的一步。-
为什么不用
store.count = 15?虽然直接赋值在插件里也能生效,但
$patch是 Pinia 推荐的批量更新 API。它确保了这次更新能被 Pinia 的 DevTools 追踪,并且能正确地触发响应式更新。 -
这里只更新
count,其他 state(比如name)保持原样。
-
📌 执行时机:这发生在 Store 刚创建、组件还没开始渲染的时候。所以用户看不到“闪一下”的变化。
4️⃣ 监听变化并持久化(核心逻辑)
store.$subscribe((_, state) => {
localStorage.setItem(key, JSON.stringify(state.count))
})
这是订阅(监听)逻辑,只要 Store 状态变了,它就可能执行。
-
store.$subscribe:Pinia 内置的监听器。它会在 state 变化之后 自动执行回调函数。 -
(_, state):-
_:代表第一个参数mutation(变化的原因)。这里用下划线忽略它,因为我们只关心结果,不关心是+1还是+10触发的。 -
state:代表 变化后的最新 state 快照。
-
-
JSON.stringify(state.count):-
state.count:拿到最新的数字(比如16)。 -
JSON.stringify:把数字转换成字符串("16"),因为 localStorage 只能存字符串。
-
-
localStorage.setItem(key, ...):把最新的count值存入浏览器。
📌 执行时机:每当你在组件中调用 store.count++、store.increment()或 $patch修改 count时,这个函数就会立即执行。
5️⃣ Action 日志(调试辅助)
store.$onAction(({ name, after }) => {
after(() => {
console.log(`[Action] ${name} completed`)
})
})
这部分和持久化无关,是调试利器。
-
store.$onAction:监听 Store 中 Actions 的调用。 -
({ name, after }):解构出 Action 的名称(如increment)和after钩子。 -
after(() => { ... }):注册一个回调,当 Action 成功执行完毕后触发。 -
效果:当你点击按钮触发
increment后,控制台会打印:[Action] increment completed
三、代码的完整执行流程图
1. main.js 加载
↓
2. createPinia().use(piniaStoragePlugins)
↓
3. 组件调用 useCountStore()
↓
4. Pinia 创建 Store 实例
↓
5. 插件开始执行
↓
6. 【恢复阶段】
读取 localStorage → 找到 pinia-count
如果找到 → $patch 更新 count
↓
7. 【监听阶段】
$subscribe 开始监听 state 变化
$onAction 开始监听 action 调用
↓
8. 组件渲染(显示恢复后的 count)
↓
9. 用户点击按钮(count 改变)
↓
10.【持久化阶段】
$subscribe 触发
将新的 count 存入 localStorage
↓
11. 页面刷新 → 回到第 6 步
四、为什么这是“高级写法”?
-
精准打击:不像有些插件傻瓜式地存整个 State,它只存
count,节省 I/O 和空间。 -
利用
$patch:遵循 Pinia 的最佳实践,保证响应式系统的完整性。 -
忽略
mutation:体现了开发者对$subscribe机制的深刻理解(只关心结果,不关心过程)。 -
生命周期管理:在 Store 初始化时恢复数据,在变化时保存数据,时机把握得非常准。
五、一句话总结
这段代码是一个“单向数据流”的典范:
Store 创建 → 读缓存 → 更新 State → State 变化 → 写缓存。
它巧妙地利用了
$subscribe作为桥梁,连接了内存中的 State 和磁盘上的 localStorage。
现在你不仅读懂了代码,还读懂了作者的意图。这段代码完全可以直接用在生产环境中。👍
需要我帮你把它改造成支持多个字段配置(比如通过 paths: ['count', 'name'])的版本吗?那是企业级项目的标准写法。
Pinia 的 Store 实例身上挂了一堆 “美元方法”($xxx),它们是 Pinia 的底层武器库。除了你刚弄明白的 $subscribe,我把它们按实战频率从高到低给你捋一遍,并告诉你什么时候用、为什么用。
一、核心必会(日常开发 90% 都在用)
1️⃣ $patch—— 批量修改 State(最重要)
作用:一次性修改多个 state,性能更好,逻辑更清晰。
// ❌ 不推荐:多次触发响应式更新
store.count++
store.name = 'pinia'
store.age = 18
// ✅ 推荐:只触发一次更新
store.$patch({
count: store.count + 1,
name: 'pinia',
age: 18
})
函数形式(适合复杂逻辑):
store.$patch((state) => {
state.list.push({ id: 1, text: 'hello' })
state.count = state.list.length
})
📌 为什么需要它:
-
避免多次触发组件重新渲染
-
让修改逻辑集中、可读
2️⃣ $state—— 直接替换整个 State
作用:用一个新对象完全替换当前 state(类似 window.location.reload的感觉)。
// 重置为初始值(SSR / 表单重置常用)
store.$state = {
count: 0,
name: '',
list: []
}
⚠️ 注意:
-
必须包含所有 state 字段,否则会丢失数据
-
通常不如
$patch精细
3️⃣ $reset—— 重置到初始状态
作用:把 state 恢复到 defineStore时的初始值。
store.$reset()
前提:
-
仅适用于 Option Store
-
Setup Store 需要自己实现
📌 典型场景:
-
关闭弹窗时清空表单
-
退出登录时重置用户状态
4️⃣ $onAction—— 监听 Actions(调试/埋点神器)
作用:监听所有 action 的调用、成功、失败。
store.$onAction(({
name, // action 名称
args, // 参数数组
after, // 成功后钩子
onError, // 失败后钩子
}) => {
console.log(`action ${name} 开始`, args)
after((result) => {
console.log(`action ${name} 成功`, result)
})
onError((error) => {
console.log(`action ${name} 失败`, error)
})
})
📌 典型场景:
-
全局埋点
-
统一的 loading 状态管理
-
错误上报
二、进阶 & 插件级(你正在学的)
5️⃣ $subscribe—— 监听 State 变化(你刚学的)
store.$subscribe((mutation, state) => {
console.log(mutation.type) // 'direct' | 'patch object' | 'patch function'
})
✅ 插件开发、持久化、日志的核心
6️⃣ $dispose—— 销毁 Store(内存管理)
作用:停止该 store 的所有响应式效果和订阅(防止内存泄漏)。
store.$dispose()
📌 什么时候用:
-
手动创建的 store(非
useXXXStore()) -
组件卸载时需要清理的特殊场景
⚠️ 注意:Vue 组件里正常使用 useXXXStore(),不需要手动调用。
三、调试 & 开发工具(DevTools)
7️⃣ $id—— Store 的唯一标识
store.$id // 'count'
📌 插件里最常用,用来区分不同的 store。
8️⃣ $onError—— 捕获 State 修改错误
作用:捕获 state 修改过程中的错误(较少用,偏底层)。
store.$onError((error) => {
console.error('State 修改出错:', error)
})
四、不常用但要知道的
9️⃣ $hotUpdate—— HMR(热更新)
store.$hotUpdate(newStore)
📌 用于 Vite / Webpack HMR,普通开发几乎碰不到。
五、一张表总结(面试/速查)
|
方法 |
核心作用 |
使用频率 |
典型场景 |
|---|---|---|---|
|
|
批量修改 state |
⭐⭐⭐⭐⭐ |
表单提交、复杂更新 |
|
|
替换整个 state |
⭐⭐⭐ |
重置、SSR |
|
|
恢复初始值 |
⭐⭐⭐⭐ |
表单清空、登出 |
|
|
监听 actions |
⭐⭐⭐⭐ |
埋点、日志、loading |
|
|
监听 state |
⭐⭐⭐⭐ |
持久化、插件 |
|
|
销毁 store |
⭐⭐ |
内存管理 |
|
|
store 标识 |
⭐⭐⭐ |
插件开发 |
六、核心区别(必背)
$patchvs 直接修改
// 直接修改:触发 N 次更新
store.count++
store.name = 'a'
// $patch:只触发 1 次更新
store.$patch({ count: 1, name: 'a' })
$subscribevs $onAction
-
$subscribe→ state 变了(结果) -
$onAction→ action 调用了(过程)
$resetvs $state =
-
$reset()→ 用初始定义(安全) -
$state =→ 用你给的对象(危险,可能缺字段)
七、实战组合拳(你现在的插件)
export function piniaStoragePlugin({ store }) {
// 1️⃣ 用 $id 做 key
const key = `pinia-${store.$id}`
// 2️⃣ 初始化恢复
const saved = localStorage.getItem(key)
if (saved) store.$patch(JSON.parse(saved))
// 3️⃣ 用 $subscribe 监听变化
store.$subscribe((_, state) => {
localStorage.setItem(key, JSON.stringify(state))
})
// 4️⃣ 用 $onAction 做日志(可选)
store.$onAction(({ name, after }) => {
after(() => {
console.log(`[Action] ${name} completed`)
})
})
}
八、一句话总结
Pinia 的
$方法是操作 Store 的“内功心法”:
改数据用
$patch监听结果用
$subscribe监听过程用
$onAction重置状态用
$reset
你现在问的这个问题,已经是 Pinia 进阶 → 插件开发 的必经之路了。
要不要我接着帮你把 $patch的源码实现原理 也拆一下?那一块对理解 Vue 响应式更新特别有帮助。
posted on 2026-07-15 16:27 luzhouxiaoshuai 阅读(8) 评论(0) 收藏 举报
浙公网安备 33010602011771号