什么是 Pinia?
Pinia 是 Vue.js 的下一代状态管理库,由 Vue.js 核心团队成员开发。它取代了 Vuex,提供了更简洁、直观的 API,同时完美支持 TypeScript。
为什么选择 Pinia?
- 直观的 API:学习曲线平缓,使用简单
- TypeScript 支持:完整的类型推断
- 模块化设计:无需复杂的模块嵌套
- DevTools 支持:优秀的开发调试体验
- 轻量级:体积小巧,性能优秀
安装与配置
bash
npm install pinia
在 main.js 中配置:
javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
核心概念
1. 创建 Store
javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'My Counter'
}),
getters: {
doubleCount: (state) => state.count * 2,
// 使用其他 getter
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
async incrementAsync() {
// 支持异步操作
await new Promise(resolve => setTimeout(resolve, 1000))
this.increment()
}
}
})
2. 组合式 API 风格
Pinia 也支持组合式 API 写法:
javascript
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const isLoggedIn = computed(() => user.value !== null)
function login(userData) {
user.value = userData
}
function logout() {
user.value = null
}
return {
user,
isLoggedIn,
login,
logout
}
})
在组件中使用
基本使用
vue
<template>
<div>
<h1>{{ counterStore.name }}</h1>
<p>Count: {{ counterStore.count }}</p>
<p>Double: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment()">+</button>
<button @click="counterStore.decrement()">-</button>
<button @click="counterStore.incrementAsync()">Async +</button>
<!-- 使用解构保持响应式 -->
<p>解构后的 Count: {{ count }}</p>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counterStore = useCounterStore()
// 正确解构方式,保持响应式
const { count, name } = storeToRefs(counterStore)
// 直接使用 action
const { increment } = counterStore
</script>
状态修改
javascript
// 方式一:直接修改
counterStore.count++
// 方式二:使用 $patch(批量更新)
counterStore.$patch({
count: counterStore.count + 1,
name: 'New Name'
})
// 方式三:使用 $patch 函数
counterStore.$patch((state) => {
state.count += 1
state.name = 'New Name'
})
// 方式四:使用 action
counterStore.increment()
// 重置状态
counterStore.$reset()
// 订阅状态变化
counterStore.$subscribe((mutation, state) => {
console.log('状态发生变化:', mutation, state)
})
高级用法
1. 数据持久化
javascript
// plugins/persistence.js
export const piniaPersistence = (context) => {
const key = `pinia-${context.store.$id}`
// 从 localStorage 恢复状态
const stored = localStorage.getItem(key)
if (stored) {
context.store.$patch(JSON.parse(stored))
}
// 订阅变化并保存
context.store.$subscribe((mutation, state) => {
localStorage.setItem(key, JSON.stringify(state))
})
}
// 在 main.js 中使用
pinia.use(piniaPersistence)
2. Store 间通信
javascript
// stores/cart.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
actions: {
addItem(item) {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('请先登录')
}
this.items.push(item)
}
}
})
3. 服务端渲染 (SSR)
javascript
// 在 setup 函数中
export default {
async setup() {
const store = useStore()
// 在服务端预取数据
if (import.meta.env.SSR) {
await store.fetchData()
}
return { store }
}
}
最佳实践
1. 组织 Store 结构
text
stores/
├── index.js # 导出所有 store
├── user.js # 用户相关状态
├── cart.js # 购物车状态
├── products.js # 商品状态
└── ui.js # UI 状态
2. 使用 TypeScript
typescript
// stores/counter.ts
import { defineStore } from 'pinia'
interface CounterState {
count: number
name: string
}
export const useCounterStore = defineStore('counter', {
state: (): CounterState => ({
count: 0,
name: 'My Counter'
}),
getters: {
doubleCount(state): number {
return state.count * 2
}
},
actions: {
increment(): void {
this.count++
}
}
})
3. 测试 Store
javascript
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '@/stores/counter'
describe('Counter Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('should increment count', () => {
const store = useCounterStore()
store.increment()
expect(store.count).toBe(1)
})
})
与 Vuex 的对比
| 特性 | Vuex | Pinia |
|---|---|---|
| API 复杂度 | 较高 | 较低 |
| TypeScript 支持 | 需要额外配置 | 原生支持 |
| 模块系统 | 嵌套模块 | 扁平结构 |
| 体积 | 较大 | 较小 |
| 学习曲线 | 较陡 | 平缓 |
总结
Pinia 为 Vue 应用提供了现代化、直观的状态管理解决方案。其简洁的 API 设计、优秀的 TypeScript 支持以及出色的开发体验,使其成为新项目的首选状态管理库。
无论是小型项目还是大型企业级应用,Pinia 都能提供良好的开发体验和可维护性。建议在新项目中直接使用 Pinia,对于现有使用 Vuex 的项目,可以根据实际情况逐步迁移。