正文
一、前言:为什么要吃透这4个核心API?
学会Pinia的Setup Store和Option Store基础写法后,想要真正灵活运用Pinia、应对复杂状态管理场景,就必须掌握它的四大核心实例API------ <math xmlns="http://www.w3.org/1998/Math/MathML"> p a t c h 、 patch、 </math>patch、reset、 <math xmlns="http://www.w3.org/1998/Math/MathML"> s u b s c r i b e 、 subscribe、 </math>subscribe、onAction。
这4个API贯穿Pinia开发全流程: <math xmlns="http://www.w3.org/1998/Math/MathML"> p a t c h 批量修改状态、 patch批量修改状态、 </math>patch批量修改状态、reset重置状态、 <math xmlns="http://www.w3.org/1998/Math/MathML"> s u b s c r i b e 监听状态变化、 subscribe监听状态变化、 </math>subscribe监听状态变化、onAction监听方法执行,覆盖"修改-重置-监听"三大核心需求。
很多开发者用Pinia只停留在基础写法,忽略了这4个API的强大功能,导致代码冗余、状态管理混乱。本文聚焦四大API,从"用法+案例+避坑"三维度拆解,全程结合Vue3 + TS实操,看完直接套用。
二、前置准备:统一Store实例(两种写法通用)
为了让API用法更直观,先定义一个通用的Pinia Store(同时兼容Option Store和Setup Store,后续API用法均基于此实例演示,避免重复):
1. Option Store 实例(基础版)
ts
// src/stores/user.ts(Option Store)
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'Pinia',
age: 3,
isLogin: false,
roles: ['user']
}),
getters: {
fullName: (state) => `Mr. ${state.name}`
},
actions: {
setLogin(status: boolean) {
this.isLogin = status
},
async fetchUserInfo() {
// 模拟接口请求
const res = await new Promise(resolve => setTimeout(() => resolve({
name: 'Pinia Pro',
age: 4,
roles: ['user', 'admin']
}), 1000))
this.$patch(res) // 后续会讲$patch用法
}
}
})
2. Setup Store 实例(基础版)
ts
// src/stores/user.ts(Setup Store)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const name = ref('Pinia')
const age = ref(3)
const isLogin = ref(false)
const roles = ref(['user'])
const fullName = computed(() => `Mr. ${name.value}`)
const setLogin = (status: boolean) => {
isLogin.value = status
}
const fetchUserInfo = async () => {
const res = await new Promise(resolve => setTimeout(() => resolve({
name: 'Pinia Pro',
age: 4,
roles: ['user', 'admin']
}), 1000))
// Setup Store 中$patch用法与Option Store一致
useUserStore().$patch({
name: res.name,
age: res.age,
roles: res.roles
})
}
return {
name, age, isLogin, roles,
fullName,
setLogin, fetchUserInfo
}
})
关键说明:以下四大API的用法,完全适用于两种Store写法,仅Setup Store中操作ref状态时需注意.value,API调用逻辑完全一致,后续不再单独区分。
三、四大核心API详解(重点!用法+案例+避坑)
按"修改状态→重置状态→监听状态→监听方法"的逻辑拆解,每个API都包含"核心作用+两种用法+实操案例+避坑点",确保新手能看懂、会用。
1. $patch:批量修改状态(高效简洁,首选)
核心作用
用于批量修改多个状态,替代多次单独修改状态(如this.name = xxx、this.age = xxx),减少代码冗余,同时优化性能(Pinia会合并多次状态更新,减少组件重渲染)。
适用场景:一次性修改多个state属性(如接口请求后,同步更新多个状态)。
两种用法(均常用,按需选择)
用法1:对象式(简单批量修改,首选)
核心:传递一个对象,对象中的key对应state的属性名,value对应要修改的值,仅修改对象中包含的属性。
ts
// 1. 组件中使用(两种Store写法通用)
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 批量修改name、age、isLogin三个状态
userStore.$patch({
name: 'Pinia Advanced',
age: 5,
isLogin: true,
// 数组类型也可直接修改(覆盖式)
roles: ['admin']
})
// 2. Option Store 的actions中使用
actions: {
updateUserInfo() {
this.$patch({ // this指向当前Store实例
name: 'Pinia Advanced',
age: 5
})
}
}
用法2:函数式(复杂修改,如数组操作)
核心:传递一个函数,函数接收state作为参数,可在函数内部执行复杂的状态修改(如数组push、splice,对象深层修改),比对象式更灵活。
ts
// 复杂修改案例:给roles数组添加新角色,同时修改age
userStore.$patch((state) => {
state.roles.push('superAdmin') // 数组操作
state.age += 1 // 计算后修改
// 支持深层修改(若state有嵌套对象)
// state.info.address = 'xxx'
})
// 对比:若用对象式,数组只能覆盖,无法直接push
userStore.$patch({
roles: [...userStore.roles, 'superAdmin'] // 需手动解构,繁琐
})
避坑点
- 对象式$patch无法修改数组的单个元素(如roles[0] = 'admin'),需用函数式或解构数组;
- Setup Store中,若直接在函数内操作ref状态,需用state.name.value(推荐直接用Store实例调用$patch,无需关注.value)。
2. $reset:重置状态(一键恢复初始值)
核心作用
将Store的所有state属性,一键恢复到初始状态(即state选项/ref定义时的初始值),无需手动逐个重置,简化逻辑。
适用场景:退出登录、表单重置、页面刷新时,恢复Store初始状态。
用法(极简,无需复杂配置)
ts
// 组件中使用(两种Store写法通用)
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 一键重置所有状态(name恢复为Pinia,age恢复为3,isLogin恢复为false)
const handleReset = () => {
userStore.$reset()
}
// Option Store 的actions中使用
actions: {
logout() {
this.setLogin(false) // 先执行自定义逻辑
this.$reset() // 再重置状态
}
}
避坑点(重点!)
- Setup Store中,$reset仅能重置"return中暴露的状态",未暴露的局部状态无法重置;
- 若state中有嵌套对象,$reset会深度重置(即嵌套对象也恢复初始值),无需额外处理;
- Option Store中,若state是箭头函数返回的对象,$reset才能生效(默认写法,无需担心)。
3. $subscribe:监听状态变化(响应式感知)
核心作用
监听Store中所有state属性的变化(单个/多个属性变化均会触发),可获取变化前、变化后的值,用于执行副作用(如日志记录、本地存储同步)。
适用场景:状态变化后执行额外逻辑(如用户信息变化时,同步缓存到localStorage)。
用法(含配置项,灵活适配场景)
ts
// 组件中使用(两种Store写法通用)
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 监听状态变化
const unsubscribe = userStore.$subscribe((mutation, state) => {
// mutation:变化相关信息(核心属性如下)
// mutation.storeId:当前Store的id(如user)
// mutation.type:变化类型(direct:直接修改,patch:$patch修改)
// mutation.payload:变化的内容(对象式$patch为修改对象,函数式为undefined)
// state:变化后的最新状态(完整state对象)
console.log('状态变化了', mutation, state)
// 示例:同步状态到localStorage
localStorage.setItem('userState', JSON.stringify(state))
}, {
// 可选配置项(按需开启)
detached: false, // 默认为false,组件卸载后自动取消监听;true则组件卸载后仍监听
deep: true, // 默认为true,深度监听嵌套对象变化;false则不监听嵌套对象
immediate: false // 默认为false,初始化时不触发;true则初始化时立即触发一次
})
// 手动取消监听(如组件卸载时)
onUnmounted(() => {
unsubscribe()
})
// Option Store 的actions中使用(较少见,一般在组件中监听)
actions: {
initSubscribe() {
this.$subscribe((mutation, state) => {
console.log('状态变化', state)
})
}
}
避坑点
- 忘记手动取消监听(detached为false时,组件卸载会自动取消;detached为true时,需手动取消,否则会内存泄漏);
- deep设为false时,嵌套对象的变化不会触发$subscribe,需根据需求开启;
- 函数式$patch修改状态时,mutation.payload为undefined,无法获取具体变化内容。
4. $onAction:监听方法执行(全生命周期感知)
核心作用
监听Store中所有actions方法的执行,可在方法执行前、执行后、执行报错时触发回调,用于拦截方法、日志记录、错误处理。
适用场景:拦截异步方法(如请求前loading、请求后处理)、记录方法执行日志、捕获方法报错。
用法(含生命周期回调,常用)
ts
// 组件中使用(两种Store写法通用)
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 监听所有actions方法的执行
const unsubscribe = userStore.$onAction(({
name, // 当前执行的actions方法名(如fetchUserInfo、setLogin)
args, // 当前方法的参数数组(如setLogin的参数[true])
after, // 方法执行成功后触发的回调
onError // 方法执行失败(报错)时触发的回调
}) => {
// 1. 方法执行前触发(可做拦截、loading等)
console.log(`方法${name}开始执行,参数:`, args)
const loading = ref(true)
// 2. 方法执行成功后触发(可做后续处理)
after((result) => {
console.log(`方法${name}执行成功,返回值:`, result)
loading.value = false
})
// 3. 方法执行失败时触发(可做错误处理)
onError((error) => {
console.error(`方法${name}执行失败,错误:`, error)
loading.value = false
ElMessage.error('操作失败,请重试')
})
}, {
// 可选配置项
detached: false // 与$subscribe一致,组件卸载后是否继续监听
})
// 手动取消监听
onUnmounted(() => {
unsubscribe()
})
// 示例:监听fetchUserInfo异步方法
userStore.fetchUserInfo() // 会触发$onAction的所有回调
避坑点
- $onAction仅能监听actions中的方法,Setup Store中未返回的局部函数、组件中的普通函数,无法监听;
- 异步方法(如fetchUserInfo)中,after回调会在方法执行完成(await结束)后触发,onError捕获await中的报错;
- 多个方法同时执行时,$onAction会分别触发对应的回调,无需担心混淆。
四、四大API对比总结(一目了然,快速选型)
| API | 核心作用 | 适用场景 | 关键注意点 |
|---|---|---|---|
| $patch | 批量修改多个状态 | 接口请求后同步更新状态、一次性修改多属性 | 函数式适配复杂修改,对象式简洁首选 |
| $reset | 一键重置所有状态为初始值 | 退出登录、表单重置、页面刷新 | Setup Store仅重置暴露的状态 |
| $subscribe | 监听所有state变化 | 状态变化后同步缓存、日志记录 | 注意取消监听,避免内存泄漏 |
| $onAction | 监听actions方法执行(全生命周期) | 异步请求拦截、错误处理、方法日志 | 仅监听actions中的方法,不监听局部函数 |
五、实战综合案例(四大API联用,贴近真实开发)
结合"用户信息管理"场景,联用四大API,实现"修改用户信息→监听状态变化→缓存到本地→重置状态→监听方法报错"的完整逻辑,代码可直接复制套用:
ts
<template>
<div class="user-manager">
<h3>用户信息管理</h3>
<p>姓名:{{ userStore.name }}</p>
<p>年龄:{{ userStore.age }}</p>
<p>角色:{{ userStore.roles.join(',') }}</p>
<button @click="updateUser">修改用户信息</button>
<button @click="resetUser">重置用户信息</button>
<button @click="fetchUser">获取用户信息(异步)</button>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
const userStore = useUserStore()
// 1. 监听状态变化,同步到localStorage
const unsubscribeSub = userStore.$subscribe((_, state) => {
localStorage.setItem('userState', JSON.stringify(state))
ElMessage.success('用户状态已同步缓存')
})
// 2. 监听actions方法执行,处理loading和错误
const unsubscribeAction = userStore.$onAction(({ name, after, onError }) => {
console.log(`方法${name}开始执行`)
const loading = ElMessage.info({ message: '操作中...', duration: 0 })
after(() => {
loading.close()
ElMessage.success(`方法${name}执行成功`)
})
onError((err) => {
loading.close()
ElMessage.error(`方法${name}执行失败:${err.message}`)
})
})
// 3. 批量修改用户信息($patch)
const updateUser = () => {
userStore.$patch({
name: 'Pinia 实战',
age: 6,
roles: ['user', 'admin', 'superAdmin']
})
}
// 4. 重置用户信息($reset)
const resetUser = () => {
userStore.$reset()
}
// 5. 异步获取用户信息(调用actions,触发$onAction)
const fetchUser = () => {
userStore.fetchUserInfo()
}
// 6. 组件卸载,取消所有监听
onUnmounted(() => {
unsubscribeSub()
unsubscribeAction()
})
</script>
六、总结:核心要点(新手必背)
- 四大API是Pinia进阶的核心,覆盖"修改-重置-监听"全流程,学会后能大幅提升状态管理效率;
- $patch:批量修改首选,对象式简洁、函数式灵活,优化性能减少重渲染;
- $reset:一键重置,注意Setup Store仅重置暴露的状态;
- <math xmlns="http://www.w3.org/1998/Math/MathML"> s u b s c r i b e / subscribe/ </math>subscribe/onAction:监听状态和方法,务必记得取消监听(避免内存泄漏),按需配置detached、deep;
- 两种Store写法(Setup/Option)中,四大API用法完全一致,仅Setup Store操作ref需注意.value。
其实这4个API的用法并不复杂,核心是"贴合场景选择"------批量修改用 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a t c h ,重置用 patch,重置用 </math>patch,重置用reset,监听状态用 <math xmlns="http://www.w3.org/1998/Math/MathML"> s u b s c r i b e ,监听方法用 subscribe,监听方法用 </math>subscribe,监听方法用onAction。结合本文的案例多练几遍,就能灵活运用到实际项目中,彻底吃透Pinia的强大之处~