Pinia 笔记:Vue3 状态管理库
1. 基础概念(大白话类比)
-
Pinia 是什么?
一个 全局数据仓库,帮你存储和管理 Vue 应用中多个组件共享的状态(数据)。类比:
- Pinia = 图书馆
- 状态(State) = 图书馆的藏书(数据)
- 获取器(Getters) = 图书馆的索引系统(快速查询)
- 动作(Actions) = 图书馆管理员(借书/还书流程,修改数据)
2. 核心概念与用法
2.1 State(状态)
- 作用:存储应用数据,类似组件的
data()
。 - 特点:
- 响应式:数据变化时自动更新使用该数据的组件。
- 全局唯一:同一 Store 在应用中只有一个实例。
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0, // 初始状态user: { name: 'Guest' }}),
})
2.2 Getters(获取器)
- 作用:计算和返回基于 State 的派生数据,类似组件的
computed
。 - 特点:
- 自动缓存:依赖的 State 不变时,不会重复计算。
export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),getters: {doubleCount: (state) => state.count * 2, // 计算双倍值// 带参数的 getter(返回函数)multiplyBy: (state) => (factor) => state.count * factor}
})
2.3 Actions(动作)
- 作用:修改 State 的方法,支持同步和异步操作。
- 特点:
- 集中管理:所有修改 State 的逻辑都在这里。
- 可追踪:便于调试和记录日志。
export const useCounterStore = defineStore('counter', {state: () => ({ count: 0, user: null }),actions: {increment() { // 同步修改this.count++},async fetchUser() { // 异步操作(示例)// 模拟 API 请求const user = await new Promise(resolve => {setTimeout(() => resolve({ name: 'Alice' }), 1000)})this.user = user}}
})
3. 在组件中使用 Pinia
<template><div><!-- 读取状态和 getter --><p>Count: {{ counter.count }}</p><p>Double: {{ counter.doubleCount }}</p><p>User: {{ counter.user?.name || 'Guest' }}</p><!-- 调用 action --><button @click="counter.increment">+1</button><button @click="() => console.log(counter.multiplyBy(3))">×3</button><button @click="counter.fetchUser">加载用户</button></div>
</template><script setup>
import { useCounterStore } from '@/stores/counter'// 获取 store 实例
const counter = useCounterStore()
</script>
4. 持久化存储(解决刷新丢失数据)
4.1 手动实现(简单场景)
// stores/counter.js
export const useCounterStore = defineStore('counter', {state: () => ({// 初始化时从本地存储读取,默认值为 0count: Number(localStorage.getItem('count')) || 0}),actions: {increment() {this.count++// 每次修改后保存到本地存储localStorage.setItem('count', this.count)}}
})
4.2 插件自动实现(推荐)
-
安装插件:
npm install pinia-plugin-persistedstate
-
注册插件:
// main.js import { createApp } from 'vue' import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import App from './App.vue'const pinia = createPinia() pinia.use(piniaPluginPersistedstate) // 启用持久化插件createApp(App).use(pinia).mount('#app')
-
在 store 中配置:
// 基础配置:自动持久化到 localStorage export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),persist: true // 关键配置 })// 高级配置 export const useUserStore = defineStore('user', {state: () => ({ name: 'Guest', age: 0 }),persist: {key: 'my_user', // 自定义存储键名(默认是 store id)storage: sessionStorage, // 使用 sessionStorage(关闭标签页清空)paths: ['name'] // 只持久化 name 字段,忽略 age} })
5. 组合式 API 风格(Vue3 推荐)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'export const useCounterStore = defineStore('counter', () => {// 状态(对应 state)const count = ref(0)const user = ref({ name: 'Guest' })// 获取器(对应 getters)const doubleCount = computed(() => count.value * 2)// 动作(对应 actions)function increment() {count.value++}async function fetchUser() {const res = await fetch('/api/user')user.value = await res.json()}// 暴露状态和方法return { count, user, doubleCount, increment, fetchUser }
}, {// 持久化配置(与选项式 API 相同)persist: true
})
6. 实际项目中的使用经验
6.1 模块化设计
- 按功能拆分 store(如
userStore.js
、cartStore.js
、settingStore.js
)。 - 示例目录结构:
src/ └── stores/├── user.js # 用户相关状态├── cart.js # 购物车相关状态└── index.js # 统一导出(可选)
6.2 避免过度使用
-
什么时候用 Pinia?
- 跨多个组件共享的数据(如用户信息、购物车)。
- 需要在多个地方修改的数据(如全局设置)。
-
什么时候不用?
- 组件内部独有的数据(如表单输入、弹窗开关)。
- 仅父子组件传递的数据(用
props
+emit
更简单)。
6.3 异步操作最佳实践
- 所有 API 请求放在
actions
中,组件只调用方法,不处理请求逻辑。 - 示例:
actions: {async fetchUser() {try {this.loading = true // 显示加载状态const res = await fetch('/api/user')this.user = await res.json()} catch (err) {console.error('加载失败', err)this.error = '加载用户失败,请重试'} finally {this.loading = false // 关闭加载状态}} }
6.4 调试技巧
- 使用 Vue DevTools 的 Pinia 面板:查看状态历史、手动修改状态。
- 监听状态变化:
const counter = useCounterStore() // 监听所有状态变化 counter.$subscribe((mutation, state) => {console.log('状态变化:', mutation, state) }) // 监听单个状态 watch(() => counter.count, (newVal) => {console.log('count 变了:', newVal) })
7. Pinia vs Vuex 对比
特性 | Pinia | Vuex |
---|---|---|
API 简洁度 | 高(无 mutations) | 中(需区分 mutations/actions) |
TypeScript 支持 | 原生支持,自动推导 | 需要手动定义类型 |
模块化 | 天然支持(多 store) | 需要配置 modules |
体积 | 小(约 2KB) | 较大 |
适用场景 | Vue3 首选 | 主要用于 Vue2 |
学习成本 | 低 | 中 |
8. 常见问题与解决方案
问题 | 解决方案 |
---|---|
刷新页面数据丢失 | 使用 pinia-plugin-persistedstate 插件 |
状态修改后 UI 未更新 | 确保通过 actions 或直接修改响应式属性 |
TypeScript 类型报错 | 使用组合式 API 风格,或手动声明接口类型 |
大型应用性能问题 | 拆分 store,减少不必要的持久化数据 |
多个 store 间相互调用 | 在一个 store 中直接引入并使用另一个 store |
9. 学习资源
- Pinia 官方文档
- pinia-plugin-persistedstate 文档
- Vue DevTools(Chrome/Firefox 插件,调试必备)