Pinia 状态管理
约 836 字大约 3 分钟
布欧-Lewyon
2026-05-15
首页 › Vue › 路由与状态管理 › Pinia 状态管理
Pinia 是 Vue 的官方状态管理库,取代 Vuex。与 Vue DevTools 深度集成,完整的 TypeScript 支持。
Pinia vs Vuex
| 对比 | Vuex | Pinia |
|---|---|---|
| TypeScript | 不友好,需额外装饰器 | ✅ 原生 TS,无需额外类型 |
| API | state/mutations/actions 四概念 | state/getters/actions 三概念(无 mutations) |
| DevTools | ✅ | ✅(更详细) |
| 模块 | 需 modules 嵌套 | Store 本身就是模块化 |
| 代码体积 | ~10KB | ~1KB |
| 学习成本 | 高 | 低(接近 Composition API 写法) |
创建 Store
Option Store(类似 Vuex)
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: '计数器'
}),
getters: {
double: (state) => state.count * 2,
display: (state) => `${state.name}: ${state.count}`
},
actions: {
increment(n = 1) {
// 直接修改 this
this.count += n
},
async fetchCount() {
const res = await fetch('/api/count')
this.count = await res.json()
}
}
})Setup Store(推荐,类似 Composition API)
// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('计数器')
const double = computed(() => count.value * 2)
function increment(n = 1) {
count.value += n
}
return { count, name, double, increment }
})在组件中使用
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
// ✅ 解构保持响应式
const { count, double } = storeToRefs(counter)
const { increment } = counter // actions 可以直接解构(非响应式)
</script>
<template>
<p>{{ count }} × 2 = {{ double }}</p>
<button @click="increment()">+1</button>
<button @click="counter.increment(5)">+5</button>
<!-- 直接修改 state -->
<button @click="counter.count++">直接改</button>
</template>Pinia 中 state 可以直接修改(不像 Vuex 必须 commit mutation)。
Getter
export const useUserStore = defineStore('user', {
state: () => ({
users: [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'editor' }
]
}),
getters: {
// 使用其他 getter(通过 this)
adminCount(): number {
return this.adminUsers.length
},
// 返回函数 → 接收参数的 getter(不缓存)
getUserById: (state) => (id: number) => {
return state.users.find(u => u.id === id)
},
adminUsers(state) {
return state.users.filter(u => u.role === 'admin')
}
}
})Action
Action 支持异步,可以在内部调用其他 store:
// stores/order.ts
import { useUserStore } from './user'
import { useCartStore } from './cart'
export const useOrderStore = defineStore('order', () => {
const orders = ref([])
const loading = ref(false)
async function submitOrder() {
loading.value = true
const user = useUserStore()
const cart = useCartStore()
try {
const res = await fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({
userId: user.id,
items: cart.items
})
})
orders.value.push(await res.json())
cart.clear() // 清空购物车
} finally {
loading.value = false
}
}
return { orders, loading, submitOrder }
})$patch
批量修改 state,只触发一次更新:
<script setup>
const store = useUserStore()
// 批量修改(对象形式)
store.$patch({
name: 'New Name',
age: 30
})
// 批量修改(函数形式,适合复杂逻辑)
store.$patch((state) => {
state.items.push({ id: 3, name: 'New' })
state.items = state.items.filter(i => i.active)
})
</script>$subscribe
监听 store 变化:
// 在组件 setup 中
const store = useCartStore()
store.$subscribe((mutation, state) => {
// mutation 包含 { storeId, type, events }
localStorage.setItem('cart', JSON.stringify(state))
})Plugins
Pinia 插件可以扩展所有 store:
// plugins/persist.ts
export function persistPlugin({ store }) {
// 从 localStorage 恢复
const saved = localStorage.getItem(store.$id)
if (saved) {
store.$patch(JSON.parse(saved))
}
// 订阅变化自动保存
store.$subscribe((_, state) => {
localStorage.setItem(store.$id, JSON.stringify(state))
})
}// main.ts
import { createPinia } from 'pinia'
import { persistPlugin } from './plugins/persist'
const pinia = createPinia()
pinia.use(persistPlugin)
app.use(pinia)实战:用户认证 Store
// stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import router from '@/router'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(localStorage.getItem('token'))
const permissions = ref([])
const isLoggedIn = computed(() => !!token.value)
const isAdmin = computed(() => user.value?.role === 'admin')
function hasPermission(code) {
return permissions.value.includes(code) || isAdmin.value
}
async function login(credentials) {
const res = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
const data = await res.json()
token.value = data.token
user.value = data.user
permissions.value = data.permissions
localStorage.setItem('token', data.token)
router.push('/dashboard')
}
async function logout() {
token.value = null
user.value = null
permissions.value = []
localStorage.removeItem('token')
router.push('/login')
}
return { user, token, permissions, isLoggedIn, isAdmin, hasPermission, login, logout }
})小结
| 概念 | 要点 |
|---|---|
| Pinia 核心 | defineStore + state / getters / actions |
| Setup Store | 推荐,Composition API 风格 |
| Option Store | 类 Vuex 风格 |
| storeToRefs | 解构后保持响应式 |
| $patch | 批量更新,一次触发 |
| Plugins | 扩展所有 store 的功能 |
上一节:导航守卫与权限路由 下一节:Vitest + Vue Test Utils
