Pinia 是 Vue 官方推荐的「全局状态管理库」,专门解决 Vue 项目中 跨组件 / 跨页面共享数据 的问题。
你可以把它理解成:
- 一个「全局数据仓库」,项目里所有组件都能访问和修改这里的数据;
- 比如电商项目的「购物车数据」、「用户登录状态」、「全局主题设置」,这些需要在多个组件中共享的信息,都适合放在 Pinia 里管理;
- 它替代了 Vue2 时代的 Vuex,是 Vue3 生态中状态管理的「标配」。
举个通俗例子:
- 没有 Pinia 时,你想把 A 组件的一个数值传给 B 组件,可能需要用「父子组件传值」「事件总线」「Vuex」等方式,步骤繁琐;
- 有了 Pinia 后,你把这个数值存在 Pinia 的 Store 里,A 组件修改、B 组件读取,直接操作这个「全局仓库」即可,无需复杂的传值逻辑。
pinia使用
在一个新的vue项目里面使用
npm i pinia@3.0.1
创建一个 stores/index.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
在main.ts里面使用pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
计数器案例
a组件可以拿到数字,b组件可以对这个数字进行累加
p1
<script setup lang="ts">
import {useCounterStore} from "@/stores/counter.ts";
const store = useCounterStore()
</script>
<template>
<div>
p1组件 number: {{ store.count }}
</div>
</template>
<style scoped>
</style>
p2
<script setup lang="ts">
import {useCounterStore} from "@/stores/counter.ts";
const store = useCounterStore()
</script>
<template>
<div>
p2组件 <button @click="store.increment">number++</button>
</div>
</template>
<style scoped>
</style>
你会发现这个写法有点像之前讲的自定义组合函数
没错,pinia和自定义组合函数有点类似,但是他们最大的区别就是pinia是全局共享的,自定义组合函数是导入一次就是一个全新的实例
选项式API
因为我之前很多项目用pinia都是选项式,所以需要讲一讲选项式的pinia,还是以计数器为例
import {defineStore} from 'pinia'
export const useUserStore = defineStore('user', {
state() {
return {
user: {
name: "枫枫",
age: 21
}
}
},
actions: {
addAge() {
this.user.age++
},
setName(name: string) {
this.user.name = name
}
},
getters: {
getAge(): number {
return this.user.age
}
}
})
使用,都是创建实例
import {useUserStore} from "@/stores/user.ts";
const store = useUserStore()
<div>
选项式API
</div>
<div>
state: name:{{ store.user.name }}
<button @click="store.addAge()">age ++ </button>
<button @click="store.setName('张三')">修改名字 </button>
getters age: {{ store.getAge }}
</div>
组合式API
加购物车案例
a组件可以加入商品到购物车,b组件展示购物车里面的商品列表
import {defineStore} from "pinia";
import {computed, ref} from "vue";
export interface carItemType {
title: string
price: number
}
export const useShoppingCarStore = defineStore("shoppingCar", () => {
const carItemList = ref<carItemType[]>([])
const totalPrice = computed(() => {
let price = 0
carItemList.value.forEach((value) => {
price += value.price
})
return price
})
function addItemByCar(item: carItemType) {
carItemList.value.push(item)
}
return {carItemList, totalPrice, addItemByCar}
})
a组件
<script setup lang="ts">
import {reactive} from "vue";
import type {carItemType} from "@/stores/shopping.ts";
import {useShoppingCarStore} from "@/stores/shopping.ts";
const data = reactive<carItemType>({
title: "",
price: 0
})
const store = useShoppingCarStore()
function addCar(){
store.addItemByCar({
title: data.title,
price: data.price,
})
data.title = ""
data.price = 0
}
</script>
<template>
<div>
加入购物车组件
<div>
<div>
<input placeholder="输入商品名称" v-model="data.title">
</div>
<div>
<input type="number" placeholder="输入商品价格" v-model.number="data.price">
</div>
</div>
<button @click="addCar">加入购物车</button>
</div>
</template>
<style scoped>
</style>
b组件
<script setup lang="ts">
import {useShoppingCarStore} from "@/stores/shopping.ts";
const store = useShoppingCarStore()
</script>
<template>
<div>
购物车展示组件
<div>
<div v-for="item in store.carItemList">
<span>商品名称: {{ item.title }}</span>
<span>商品价格: {{ item.price }}¥</span>
</div>
<div>总金额:{{ store.totalPrice }}¥</div>
</div>
</div>
</template>
<style scoped>
</style>
Store 实例内置方法
$patch 批量修改 State
- 作用:批量更新多个 State 属性,相比逐个赋值更高效;支持「对象形式」和「函数形式」,函数形式适合复杂逻辑(如数组操作)。
- 场景:需要同时修改多个状态,或修改数组 / 对象类状态时。
function patch() {
// 对象形式,适合批量修改
formStore.$patch({
// user: {
// name: "枫枫"
// },
// age: 18,
addr: "湖南长沙"
})
// 函数形式,适合自修改等复杂的修改逻辑
formStore.$patch((state) => {
state.age++
state.addr = "四川成都"
})
}
$reset 重置 State 到初始值
- 作用:将 Store 的所有 State 恢复到定义时的初始状态。
- 场景:表单重置、页面刷新后恢复初始状态、操作错误后回滚等。
function reset() {
formStore.$reset()
}
Store "shoppingCar" is built using the setup syntax and does not implement $reset().
需要注意,setup语法糖下,组合式api不能用
$subscribe 监听store变化
- 作用:订阅 State 的所有修改,能捕获到「谁修改了 State」「修改了什么」「修改前 / 后的值」。
- 场景:状态变更日志、自动持久化、修改后触发其他逻辑(如埋点、请求)。
const unsubscribe = formStore.$subscribe((mutation, state) => {
// mutation:修改信息对象
// mutation.type:修改类型(direct:直接赋值;patch object:$patch 对象;patch function:$patch 函数)
// mutation.storeId:当前 Store 的唯一 ID
// mutation.payload:修改的内容(仅 patch object 时有值)
// state 修改之后的值
console.log("$subscribe", {
mutation,
state,
})
}, {immediate: true}) // 第二个参数和watch的第三个参数差不多
// watch可以实现监听某一个值的变化
const unWatch = watch(()=>formStore.user.name, ()=>{
console.log("formStore name 发生变化")
})
onUnmounted(()=>{
unsubscribe()
unWatch.stop()
})
和watch的区别
- watch可以实现监听单个值的变化
- 监听整个store的时候,$subscribe性能更好一点
- $subscribe可以区分修改类型 直接修改,还是$patch
$onAction 监听 Action 执行
- 作用:订阅 Store 中 Action 的执行过程,能捕获 Action 的「开始、成功、失败」状态。
- 场景:Action 执行埋点、异步 Action 错误监控、记录操作日志。
const counterStore = useCounterStore()
counterStore.$onAction(({
name, // Action 名称(如 'incrementAsync')
args, // Action 调用时的参数数组
after, // Action 成功执行后的回调
onError, // Action 执行失败后的回调
store // 当前 Store 实例
}) => {
console.log(`Action【${name}】开始执行,参数:`, args)
// Action 成功后执行
after((result) => {
console.log(`Action【${name}】执行成功,返回值:`, result)
})
// Action 报错后执行
onError((error) => {
console.error(`Action【${name}】执行失败,错误:`, error)
})
})
// 调用 Action 时会触发上面的监听
counterStore.incrementAsync(2)
持久化store
pinia里面的数据在刷新之后会重新赋值
如何让他实现持久化功能呢
自己实现持久化功能
本身这个功能的实现很简单,直接使用$subscribe监听store的变化
变化了就写入localStorage,在初始化的时候从localStorage里面去读取,然后写入到store即可
import {defineStore} from "pinia";
export const useLocalStore = defineStore("local", {
state(){
return {
name: "",
addr: ""
}
}
})
export function load(){
const store = useLocalStore()
const data = localStorage.getItem(store.$id)
if (data){
try {
const _data = JSON.parse(data)
store.$patch(_data)
}catch (e) {
console.log(e)
}
}
store.$subscribe((mutation, state) =>{
// console.log("$subscribe", mutation.storeId, state)
localStorage.setItem(mutation.storeId, JSON.stringify(state))
})
}
在app.vue里面引入
<template>
<div>
<a-form>
<a-form-item label="name">
<a-input v-model="store.name"></a-input>
</a-form-item>
<a-form-item label="addr">
<a-textarea v-model="store.addr"></a-textarea>
</a-form-item>
</a-form>
</div>
</template>
<script lang="ts" setup>
import {load, useLocalStore} from "@/stores/local.ts";
const store = useLocalStore()
load()
</script>
持久化插件
npm install pinia-plugin-persistedstate
使用持久化插件
import { createApp } from 'vue'
import { createPinia } from 'pinia'
// 导入持久化插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
// 创建 Pinia 实例
const pinia = createPinia()
// 注册插件
pinia.use(piniaPluginPersistedstate)
const app = createApp(App)
app.use(pinia)
app.mount('#app')
开启store持久化
只需在定义 Store 时添加 persist: true 配置(选项式 / 组合式写法都支持):
选项式
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
msg: 'Hello Pinia'
}),
actions: {
increment() {
this.count++
}
},
// 开启持久化(核心配置)
persist: true
})
组合式
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
const name = ref('张三')
const age = ref(20)
return { name, age }
}, {
// 组合式写法需在第三个参数中配置 persist
persist: true
})
持久化高级配置
persist: true 是默认配置,实际开发中通常需要自定义存储位置、存储 key、持久化字段等,只需把 persist 改成配置对象:
import {defineStore} from "pinia";
import {ref} from "vue";
// export const useLocal1Store = defineStore("local1", {
// state(){
// return {
// name: "",
// addr: ""
// }
// },
// persist: true
// })
// export const useLocal1Store = defineStore("local1", () => {
// const name = ref("")
// const addr = ref("")
// return {name, addr}
// }, {persist: true})
export const useLocal1Store = defineStore("local1", () => {
const name = ref("")
const addr = ref("")
return {name, addr}
}, {
persist: {
key: "xxx-id", // 1. 自定义存储的 key(默认是 Store ID)
storage: sessionStorage, // 2. 自定义存储介质(默认 localStorage,可选 sessionStorage)
// pick: ["name"], // 哪些字段做持久化
// omit: ["name"], // 除了这些字段都做持久化
serializer: { // 自定义序列化/反序列化(比如加密/解密)
// 存储时序列化(默认 JSON.stringify)
serialize: (value) => {
// 示例:简单加密(实际项目用专业加密库)
return btoa(JSON.stringify(value))
},
// 读取时反序列化(默认 JSON.parse)
deserialize: (value) => {
return JSON.parse(atob(value))
}
}
}
})