5.1 为什么选择 Pinia?
Pinia 是 Vue 3 的官方推荐状态管理库(Vuex 5 的精神继承者),由 Vue 核心团队成员 Eduardo San Martin Morote 开发。与 Vuex 4 相比,Pinia 有以下优势:
| 特性 | Vuex 4 | Pinia |
|---|---|---|
| TypeScript | 类型推断差,需要大量类型声明 | 原生 TS,完全自动推断 |
| 代码量 | 需要 mutation + action,繁琐 | 只有 state + getters + actions,简洁 |
| 模块化 | 需要 modules,命名空间复杂 | 天然多 Store,互相引用方便 |
| DevTools | 支持 | 支持(更好的可视化) |
| SSR | 支持 | 更好的 SSR 支持 |
| 体积 | ~10KB | ~1.5KB(微型) |
5.2 安装与初始化
npm install pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const pinia = createPinia()
createApp(App)
.use(pinia)
.use(router)
.mount('#app')
5.3 Options Store(选项式风格)
与 Vue 的 Options API 类似,适合从 Vuex 迁移的开发者:
import { defineStore } from 'pinia'
// 惯例:Store ID 以 'use' 开头,以 'Store' 结尾
export const useCounterStore = defineStore('counter', {
// state:返回初始状态的函数
state() {
return {
count: 0,
name: '计数器'
}
},
// getters:相当于计算属性(自动缓存)
getters: {
doubleCount: (state) => state.count * 2,
// 在 getter 中调用其他 getter
doubleCountPlusOne(): number {
return this.doubleCount + 1
}
},
// actions:同步/异步操作(替代 Vuex 的 mutation + action)
actions: {
increment() {
this.count++ // 直接修改 state!
},
reset() {
this.count = 0
},
async fetchAndSet() {
const data = await fetch('/api/count').then(r => r.json())
this.count = data.count
}
}
})
5.4 Setup Store(组合式风格,推荐)
与 Vue 的 Composition API 一致,更灵活,TypeScript 支持更佳:
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
interface User {
id: number
name: string
email: string
role: 'admin' | 'user'
}
export const useUserStore = defineStore('user', () => {
// ref 对应 state
const currentUser = ref<User | null>(null)
const isLoading = ref(false)
const token = ref('')
// computed 对应 getters
const isLoggedIn = computed(() => !!currentUser.value)
const isAdmin = computed(() => currentUser.value?.role === 'admin')
const userName = computed(() => currentUser.value?.name ?? '游客')
// function 对应 actions
async function login(email: string, password: string) {
isLoading.value = true
try {
const res = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
})
const data = await res.json()
currentUser.value = data.user
token.value = data.token
} finally {
isLoading.value = false
}
}
function logout() {
currentUser.value = null
token.value = ''
}
// 必须 return 所有需要暴露的内容
return { currentUser, isLoading, isLoggedIn, isAdmin, userName, login, logout }
})
5.5 在组件中使用 Store
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'
import { useCounterStore } from '@/stores/counter'
const userStore = useUserStore()
const counterStore = useCounterStore()
// ✅ 使用 storeToRefs 解构 state 和 getters(保持响应性)
// 注意:actions 不需要解构,直接调用即可
const { isLoggedIn, userName, isLoading } = storeToRefs(userStore)
// 直接解构 actions(不影响响应性,因为 action 是函数)
const { login, logout } = userStore
// ❌ 错误:直接解构 state/getters 会丢失响应性
// const { isLoggedIn } = userStore // isLoggedIn 变成普通布尔值
</script>
<template>
<div v-if="isLoading">加载中...</div>
<div v-else-if="isLoggedIn">
欢迎, {{ userName }}
<button @click="logout">退出</button>
</div>
<button v-else @click="login('a@b.com', '123456')">登录</button>
</template>
5.6 Store 间的互相引用
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', () => {
// 在 action/getter 内部调用其他 store(不要在 setup 顶层调用)
async function checkout() {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('请先登录')
}
// 继续结算逻辑...
}
return { checkout }
})
5.7 持久化插件
使用 pinia-plugin-persistedstate 将 Store 状态持久化到 localStorage/sessionStorage:
npm install pinia-plugin-persistedstate
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) // 注册插件
export const useUserStore = defineStore('user', () => {
const token = ref('')
const user = ref(null)
return { token, user }
}, {
// 持久化配置
persist: {
key: 'user-store', // localStorage 键名
storage: localStorage, // 存储目标
paths: ['token'], // 只持久化 token,不存 user 对象
}
})
$patch 批量修改
Pinia 提供了 $patch 方法批量修改多个 state,比多次单独赋值更高效(减少视图更新次数):counterStore.$patch({ count: 10, name: '新名称' })。也可以传入函数:store.$patch(state => { state.count++; state.list.push(item) })。
$reset 重置状态
Options Store 自动拥有 $reset() 方法,可将 state 重置为初始值。Setup Store 需要手动实现 reset 函数。