一、Pinia 简介
1.1 什么是 Pinia?
Pinia 是 Vue 官方推荐的状态管理库,是 Vuex 的继任者。它简化了状态管理逻辑,原生支持 TypeScript,且与 Vue3 的 Composition API 完美契合。本文将从基础用法到高级技巧,全面讲解 Pinia 的使用。
1.2 为什么选择 Pinia?
- ✅ 类型安全:完美的 TypeScript 支持
- ✅ 开发工具:集成 Vue DevTools
- ✅ 模块化:不需要嵌套模块
- ✅ 轻量级:体积小巧,约 1KB
- ✅ 组合式 API:完美支持 Vue3 组合式 API
- ✅ 零依赖:不依赖其他库
二、快速开始
2.1 安装
npm install pinia
# 或
yarn add pinia
# 或
pnpm add pinia
2.2 基本配置
// main.js
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')
三、核心概念
3.1 定义 Store
选项式风格
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 状态
state: () => ({
count: 0,
name: '计数器'
}),
// 计算属性
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
// 操作方法
actions: {
increment() {
this.count++
},
async incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
this.increment()
}
}
})
组合式风格
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// 状态
const user = ref(null)
const token = ref('')
// 计算属性
const isLoggedIn = computed(() => !!token.value)
// 操作方法
function login(userData) {
user.value = userData
token.value = 'token'
}
function logout() {
user.value = null
token.value = ''
}
async function fetchUserProfile() {
const response = await fetch('/api/user')
user.value = await response.json()
}
return {
user,
token,
isLoggedIn,
login,
logout,
fetchUserProfile
}
})
3.2 在组件中使用 Store
<template>
<div>
<h2>{{ counterStore.name }}</h2>
<p>计数: {{ counterStore.count }}</p>
<p>双倍计数: {{ counterStore.doubleCount }}</p>
<p>双倍计数+1: {{ counterStore.doubleCountPlusOne }}</p>
<button @click="counterStore.increment()">+1</button>
<button @click="counterStore.incrementAsync()">异步+1</button>
<button @click="reset">重置</button>
<div v-if="userStore.isLoggedIn">
欢迎, {{ userStore.user?.name }}
<button @click="userStore.logout()">退出</button>
</div>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
// 使用 store
const counterStore = useCounterStore()
const userStore = useUserStore()
// 方法可以直接使用
const reset = () => {
counterStore.$reset() // 重置到初始状态
}
// 使用 storeToRefs 保持响应式
const { count, doubleCount } = storeToRefs(counterStore)
</script>
四、高级特性
4.1 状态持久化
// stores/persisted-store.js
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
export const usePersistedStore = defineStore('persisted', () => {
const state = ref(JSON.parse(localStorage.getItem('app-state') || '{}'))
// 监听状态变化并持久化
watch(
state,
(newState) => {
localStorage.setItem('app-state', JSON.stringify(newState))
},
{ deep: true }
)
function updateState(key, value) {
state.value[key] = value
}
return {
state,
updateState
}
})
4.2 插件系统
// plugins/persistence.js
export const persistencePlugin = ({ store }) => {
// 从 localStorage 恢复状态
const stored = localStorage.getItem(`pinia-${store.$id}`)
if (stored) {
store.$patch(JSON.parse(stored))
}
// 监听变化并保存
store.$subscribe((mutation, state) => {
localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(state))
})
}
// main.js
import { createPinia } from 'pinia'
import { persistencePlugin } from './plugins/persistence'
const pinia = createPinia()
pinia.use(persistencePlugin)
4.3 Store 间通信
// stores/cart.js
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
actions: {
addItem(product) {
this.items.push(product)
}
}
})
// stores/notification.js
import { defineStore } from 'pinia'
export const useNotificationStore = defineStore('notification', {
state: () => ({
messages: []
}),
actions: {
showMessage(message) {
this.messages.push(message)
setTimeout(() => {
this.messages.shift()
}, 3000)
}
}
})
// 在组件中使用
import { useCartStore } from '@/stores/cart'
import { useNotificationStore } from '@/stores/notification'
const cartStore = useCartStore()
const notificationStore = useNotificationStore()
const addToCart = (product) => {
cartStore.addItem(product)
notificationStore.showMessage('商品已添加到购物车')
}
五、最佳实践
5.1 项目结构
src/
├── stores/
│ ├── index.js # Store 导出文件
│ ├── app.js # 应用级别状态
│ ├── auth.js # 认证状态
│ ├── user.js # 用户数据
│ ├── products.js # 商品数据
│ └── cart.js # 购物车
└── components/
└── ...
5.2 Store 导出文件
// stores/index.js
export { useAppStore } from './app'
export { useAuthStore } from './auth'
export { useUserStore } from './user'
export { useProductsStore } from './products'
export { useCartStore } from './cart'
// 在组件中导入
import { useAuthStore, useUserStore } from '@/stores'
5.3 类型安全(TypeScript)
// stores/types.ts
export interface User {
id: number
name: string
email: string
role: 'admin' | 'user'
}
export interface Product {
id: number
name: string
price: number
category: string
}
// stores/user.ts
import { defineStore } from 'pinia'
import type { User } from './types'
interface UserState {
user: User | null
token: string | null
preferences: {
theme: 'light' | 'dark'
language: string
}
}
export const useUserStore = defineStore('user', {
state: (): UserState => ({
user: null,
token: null,
preferences: {
theme: 'light',
language: 'zh-CN'
}
}),
getters: {
isLoggedIn: (state) => !!state.token,
userName: (state) => state.user?.name || 'Guest'
},
actions: {
setUser(user: User) {
this.user = user
},
updatePreferences(preferences: Partial<UserState['preferences']>) {
this.preferences = { ...this.preferences, ...preferences }
}
}
})
5.4 测试 Store
// __tests__/counter.store.spec.js
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '@/stores/counter'
describe('Counter Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('should increment count', () => {
const store = useCounterStore()
expect(store.count).toBe(0)
store.increment()
expect(store.count).toBe(1)
})
it('should compute double count', () => {
const store = useCounterStore()
store.count = 5
expect(store.doubleCount).toBe(10)
})
it('should reset state', () => {
const store = useCounterStore()
store.count = 10
store.$reset()
expect(store.count).toBe(0)
})
})
六、性能优化
6.1 选择性响应式
<template>
<div>
<!-- 只监听需要的状态 -->
<p>用户名: {{ userName }}</p>
<p>主题: {{ theme }}</p>
</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
// 只解构需要的数据,避免不必要的响应式
const { userName, theme } = storeToRefs(userStore)
// 或者直接访问,不创建 ref
const userName = computed(() => userStore.userName)
const theme = computed(() => userStore.preferences.theme)
</script>
6.2 批量更新
// 不好的做法 - 多次更新
userStore.updateName('新名字')
userStore.updateEmail('new@email.com')
userStore.updateTheme('dark')
// 好的做法 - 批量更新
userStore.$patch({
user: {
...userStore.user,
name: '新名字',
email: 'new@email.com'
},
preferences: {
...userStore.preferences,
theme: 'dark'
}
})
七、调试技巧
7.1 Vue DevTools
// 在 Store 中添加调试信息
export const useDebugStore = defineStore('debug', {
state: () => ({
debug: process.env.NODE_ENV === 'development',
logs: []
}),
actions: {
log(action, payload) {
if (this.debug) {
this.logs.push({
timestamp: new Date(),
action,
payload
})
console.log(`[Store Log] ${action}:`, payload)
}
}
}
})
7.2 状态快照
// 保存和恢复状态快照
const saveSnapshot = () => {
const snapshot = pinia.state.value
localStorage.setItem('pinia-snapshot', JSON.stringify(snapshot))
}
const restoreSnapshot = () => {
const snapshot = localStorage.getItem('pinia-snapshot')
if (snapshot) {
pinia.state.value = JSON.parse(snapshot)
}
}
八、迁移指南
8.1 从 Vuex 迁移到 Pinia
| Vuex | Pinia | 说明 |
|---|---|---|
state | state() | 基本相同 |
getters | getters | 基本相同 |
mutations | actions | Pinia 没有 mutations |
actions | actions | 支持异步操作 |
modules | 多个 store | 每个模块一个 store |
8.2 迁移示例
Vuex:
// vuex store
export default {
namespaced: true,
state: {
count: 0
},
getters: {
doubleCount: state => state.count * 2
},
mutations: {
INCREMENT(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('INCREMENT')
}, 1000)
}
}
}
Pinia:
// pinia store
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
},
async incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
this.increment()
}
}
})
九、总结
Pinia 提供了现代化、类型安全的状态管理解决方案。通过本指南,你应该能够:
- ✅ 理解 Pinia 的核心概念
- ✅ 在项目中正确使用 Pinia
- ✅ 实现复杂的状态管理逻辑
- ✅ 优化应用性能
- ✅ 调试和测试 Store
- ✅ 从 Vuex 平滑迁移
记住,状态管理的目标是让应用的状态变化可预测、易于调试。合理使用 Pinia,可以让你的 Vue3 应用更加健壮和可维护。
463

被折叠的 条评论
为什么被折叠?



