mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3
645 字
2 分钟
Pinia 完全指南:Vue 3 状态管理的最佳实践
2026-05-15

🍍 什么是 Pinia?#

Pinia 是 Vue 生态系统的官方状态管理库,由 Vue.js 核心团队维护。它是 Vuex 的继任者,专为 Vue 3 设计,提供了更简洁、更强大的状态管理解决方案。

为什么选择 Pinia?#

1. 开箱即用的 TypeScript 支持#

stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: '' as string,
age: 0 as number,
isAdmin: false
}),
getters: {
adult: (state) => state.age >= 18
},
actions: {
setName(name: string) {
this.name = name
}
}
})

2. 极简的 API 设计#

  • 无需复杂的配置和设置
  • 去除了 Vuex 的 mutations
  • 支持组合式 API 和选项式 API

3. DevTools 支持#

  • 完整的 Vue DevTools 集成
  • 时间旅行调试
  • 状态快照和回滚

4. 自动化代码分割#

  • 每个 store 都是独立的模块
  • 按需加载,优化性能
  • 完美的 Tree-shaking 支持

🔄 Pinia vs Vuex#

核心差异对比#

特性PiniaVuex
** mutations**❌ 不需要✅ 必须使用
** TypeScript**✅ 原生支持⚠️ 需要额外配置
模块化✅ 自动支持⚠️ 需要手动配置
组合式 API✅ 完美支持⚠️ 有限支持
学习曲线🟢 平缓🟡 陡峭

代码对比示例#

Vuex 4 写法#

stores/user.js
import { createStore } from 'vuex'
export default createStore({
state: {
user: null,
token: ''
},
mutations: {
SET_USER(state, user) {
state.user = user
},
SET_TOKEN(state, token) {
state.token = token
}
},
actions: {
async login({ commit }, credentials) {
const { user, token } = await api.login(credentials)
commit('SET_USER', user)
commit('SET_TOKEN', token)
}
},
getters: {
isAuthenticated: (state) => !!state.token
}
})

Pinia 写法(更简洁)#

stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
token: ''
}),
actions: {
async login(credentials) {
const { user, token } = await api.login(credentials)
this.user = user // 直接修改 state
this.token = token
}
},
getters: {
isAuthenticated: () => !!this.token
}
})

🚀 快速开始#

安装配置#

# npm
npm install pinia
# yarn
yarn add pinia
# pnpm
pnpm add pinia

基础设置#

main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')

📦 Store 的定义和使用#

选项式 API(Options Store)#

stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 状态
state: () => ({
count: 0,
name: 'My Counter'
}),
// 计算属性
getters: {
doubleCount: (state) => state.count * 2,
displayName(): string {
return `${this.name} (${this.count})`
}
},
// 方法
actions: {
increment() {
this.count++
},
reset() {
this.count = 0
},
async fetchCount() {
const response = await fetch('/api/count')
this.count = await response.json()
}
}
})

组合式 API(Setup Store)#

stores/todo.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useTodoStore = defineStore('todo', () => {
// state
const todos = ref<Todo[]>([])
const filter = ref<'all' | 'active' | 'completed'>('all')
// getters
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active': return todos.value.filter(t => !t.completed)
case 'completed': return todos.value.filter(t => t.completed)
default: return todos.value
}
})
const stats = computed(() => ({
total: todos.value.length,
active: todos.value.filter(t => !t.completed).length,
completed: todos.value.filter(t => t.completed).length
}))
// actions
async function fetchTodos() {
const response = await fetch('/api/todos')
todos.value = await response.json()
}
function addTodo(text: string) {
todos.value.push({
id: Date.now(),
text,
completed: false
})
}
function toggleTodo(id: number) {
const todo = todos.value.find(t => t.id === id)
if (todo) todo.completed = !todo.completed
}
return {
todos,
filter,
filteredTodos,
stats,
fetchTodos,
addTodo,
toggleTodo
}
})

在组件中使用#

<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
import { useTodoStore } from '@/stores/todo'
// 使用 store
const counter = useCounterStore()
const todo = useTodoStore()
// 直接访问状态
console.log(counter.count)
// 调用 actions
counter.increment()
// 使用 getters
console.log(counter.doubleCount)
// 在模板中使用
</script>
<template>
<div>
<h1>{{ counter.name }}</h1>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment">+</button>
<button @click="counter.reset">Reset</button>
</div>
</template>

🎯 高级特性#

1. Store 的组合使用#

stores/auth.ts
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null as User | null,
token: ''
}),
actions: {
async login(credentials) {
const { user, token } = await api.login(credentials)
this.user = user
this.token = token
}
}
})
// stores/settings.ts
export const useSettingsStore = defineStore('settings', {
state: () => ({
theme: 'light' as 'light' | 'dark',
language: 'zh-CN'
})
})
// 在组件中同时使用多个 store
<script setup>
import { useAuthStore } from '@/stores/auth'
import { useSettingsStore } from '@/stores/settings'
const auth = useAuthStore()
const settings = useSettingsStore()
</script>

2. 持久化存储#

stores/main.ts
import { defineStore } from 'pinia'
import { useStorage } from '@vueuse/core'
export const useMainStore = defineStore('main', {
state: () => ({
userPreferences: useStorage('prefs', {
theme: 'light',
fontSize: 16
}),
sidebarOpen: useStorage('sidebar', true)
})
})

3. 插件系统#

plugins/pinia-persist.ts
import { PiniaPluginContext } from 'pinia'
export function persistPlugin({ store }: PiniaPluginContext) {
// 从 localStorage 恢复状态
const saved = localStorage.getItem(`pinia-${store.$id}`)
if (saved) {
store.$patch(JSON.parse(saved))
}
// 监听状态变化并保存
store.$subscribe(() => {
localStorage.setItem(
`pinia-${store.$id}`,
JSON.stringify(store.$state)
)
})
}
// main.ts
import { createPinia } from 'pinia'
import { persistPlugin } from './plugins/pinia-persist'
const pinia = createPinia()
pinia.use(persistPlugin)

4. Store 的监听和订阅#

// 订阅状态变化
const counter = useCounterStore()
// $subscribe 订阅状态变化
counter.$subscribe((mutation, state) => {
console.log('State changed:', mutation, state)
// 可以在这里实现持久化、日志记录等
})
// $onAction 订阅 action 调用
counter.$onAction(({ name, args, after }) => {
console.log(`Action ${name} called with`, args)
// 在 action 完成后执行
after((result) => {
console.log(`Action ${name} finished with`, result)
})
})
// $reset 重置到初始状态
counter.$reset()

🔥 实战项目示例#

电商购物车 Store#

stores/cart.ts
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export interface CartItem {
id: number
name: string
price: number
quantity: number
image: string
}
export const useCartStore = defineStore('cart', () => {
// 状态
const items = ref<CartItem[]>([])
const coupon = ref<string | null>(null)
const discount = ref(0)
// 计算属性
const subtotal = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
const total = computed(() =>
subtotal.value - discount.value
)
const itemCount = computed(() =>
items.value.reduce((sum, item) => sum + item.quantity, 0)
)
const isEmpty = computed(() => items.value.length === 0)
// Actions
function addItem(product: Product) {
const existingItem = items.value.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity++
} else {
items.value.push({
id: product.id,
name: product.name,
price: product.price,
quantity: 1,
image: product.image
})
}
}
function removeItem(id: number) {
const index = items.value.findIndex(item => item.id === id)
if (index > -1) {
items.value.splice(index, 1)
}
}
function updateQuantity(id: number, quantity: number) {
const item = items.value.find(item => item.id === id)
if (item) {
item.quantity = Math.max(0, quantity)
}
}
function clearCart() {
items.value = []
coupon.value = null
discount.value = 0
}
async function applyCoupon(code: string) {
const response = await fetch('/api/coupons/validate', {
method: 'POST',
body: JSON.stringify({ code })
})
const { valid, discount: amount } = await response.json()
if (valid) {
coupon.value = code
discount.value = amount
}
}
async function checkout() {
const order = await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({
items: items.value,
coupon: coupon.value,
total: total.value
})
}).then(r => r.json())
clearCart()
return order
}
return {
items,
coupon,
discount,
subtotal,
total,
itemCount,
isEmpty,
addItem,
removeItem,
updateQuantity,
clearCart,
applyCoupon,
checkout
}
})

用户认证 Store#

stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null)
const token = ref<string | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const isAuthenticated = computed(() => !!token.value)
const userDisplayName = computed(() =>
user.value ? user.value.name : 'Guest'
)
async function login(credentials: LoginCredentials) {
loading.value = true
error.value = null
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
})
if (!response.ok) {
throw new Error('Login failed')
}
const { user: userData, token: userToken } = await response.json()
user.value = userData
token.value = userToken
// 保存到 localStorage
localStorage.setItem('auth-token', userToken)
} catch (err) {
error.value = err instanceof Error ? err.message : 'Login failed'
throw err
} finally {
loading.value = false
}
}
async function logout() {
user.value = null
token.value = null
localStorage.removeItem('auth-token')
}
async function fetchUser() {
const savedToken = localStorage.getItem('auth-token')
if (!savedToken) return
loading.value = true
try {
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${savedToken}`
}
})
if (response.ok) {
user.value = await response.json()
token.value = savedToken
}
} finally {
loading.value = false
}
}
return {
user,
token,
loading,
error,
isAuthenticated,
userDisplayName,
login,
logout,
fetchUser
}
})

🌟 最佳实践#

1. 合理的 Store 划分#

// ✅ 好的做法 - 按功能划分
stores/
├── auth.ts // 认证相关
├── cart.ts // 购物车相关
├── products.ts // 产品相关
└── ui.ts // UI 状态
// ❌ 不好的做法 - 按技术划分
stores/
├── state.ts // 所有状态
├── getters.ts // 所有计算属性
└── actions.ts // 所有方法

2. 使用 TypeScript 类型安全#

stores/user.ts
interface User {
id: number
name: string
email: string
role: 'admin' | 'user' | 'guest'
}
interface UserState {
user: User | null
isAuthenticated: boolean
}
export const useUserStore = defineStore('user', {
state: (): UserState => ({
user: null,
isAuthenticated: false
})
})

3. 组合式 API 优先#

// ✅ 推荐 - 组合式 API
export const useStore = defineStore('store', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() { count.value++ }
return { count, double, increment }
})
// ⚠️ 可选 - 选项式 API
export const useStore = defineStore('store', {
state: () => ({ count: 0 }),
getters: {
double: state => state.count * 2
},
actions: {
increment() { this.count++ }
}
})

4. 避免过度使用#

<!-- ❌ 不好的做法 - 简单的本地状态不需要 Store -->
<script setup>
import { useStore } from '@/stores/store'
const store = useStore()
</script>
<template>
<input v-model="store.inputValue" /> <!-- 应该使用本地状态 -->
</template>
<!-- ✅ 好的做法 - 本地状态使用 ref -->
<script setup>
import { ref } from 'vue'
const inputValue = ref('')
</script>
<template>
<input v-model="inputValue" />
</template>

📚 性能优化#

1. 懒加载 Store#

// 只在需要时才加载 store
async function loadAdminPanel() {
const { useAdminStore } = await import('@/stores/admin')
const admin = useAdminStore()
// 使用 admin store
}

2. 避免不必要的响应式#

// ✅ 好的做法 - 静态数据不需要响应式
export const useConfigStore = defineStore('config', {
state: () => ({
apiConfig: {
baseUrl: import.meta.env.VITE_API_URL,
timeout: 5000
} as const // 使用 as const 避免深层响应式
})
})

3. 使用 shallowRef#

import { shallowRef } from 'vue'
export const useDataStore = defineStore('data', () => {
// 大型对象使用 shallowRef
const largeData = shallowRef<LargeObject>(null)
return { largeData }
})

🎓 总结#

Pinia 作为 Vue 3 的官方状态管理库,提供了:

  • 简单直观的 API,无需复杂的配置
  • 完美的 TypeScript 支持,开发体验优秀
  • 强大的组合能力,支持复杂的状态管理需求
  • 优秀的性能表现,自动代码分割和懒加载
  • 完整的 DevTools 集成,调试体验一流

无论是小型项目还是大型应用,Pinia 都是一个值得选择的现代状态管理解决方案。

TIP

开始使用 Pinia 的最佳时机:

  • 新项目直接使用 Pinia
  • 现有项目可以考虑逐步迁移
  • Vue 3 项目首选 Pinia 而非 Vuex

相关阅读:

有问题? 欢迎在评论区讨论你的 Pinia 使用经验!

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Pinia 完全指南:Vue 3 状态管理的最佳实践
https://basyc.cloud/posts/pinia-guide/
作者
北汐-Basyc
发布于
2026-05-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录