pinia-vue3官方状态库学习
Pinia 是 Vue 官方推荐的「全局状态管理库」,专门解决 Vue 项目中 跨组件 / 跨页面共享数据 的问题。 你可以把它理解成: 一个「全局数据仓库」,项目里所有组件都能访问和修改这里的

pinia-vue3官方状态库学习

发布时间:2025-12-20 (2025-12-20)

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的区别

  1. watch可以实现监听单个值的变化
  2. 监听整个store的时候,$subscribe性能更好一点
  3. $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))
            }
        }
    }
})