一、Pinia 概述
Pinia 是 Vue 3 的官方推荐状态管理库,由 Vue 核心团队维护。它是对 Vuex 的改进和简化,提供了更简洁的 API 和更好的 TypeScript 支持。
Pinia 的核心优势
- 更简单的 API:相比 Vuex 减少了概念和模板代码
- 完美的 TypeScript 支持:完整的类型推断
- 模块化设计:无需嵌套模块,每个 store 都是独立的
- 轻量级:体积小,压缩后约 1KB
- Composition API 风格:与 Vue 3 的 Composition API 完美契合
二、Pinia 核心概念
1. Store 定义
import { defineStore } from 'pinia'
// 使用选项式 API 风格定义 store
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
// 使用组合式 API 风格定义 store
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
function setUser(newUser: User) {
user.value = newUser
}
return { user, setUser }
})
2. State
State 是 store 的核心部分,存储应用程序的状态:
const store = useCounterStore()
// 直接访问
console.log(store.count)
// 重置状态
store.$reset()
// 变更状态(不推荐直接修改,建议使用 actions)
store.count++
// 使用 $patch 批量修改
store.$patch({
count: store.count + 1,
name: 'newName'
})
// 使用函数形式 $patch
store.$patch((state) => {
state.items.push({ name: 'shoes' })
state.hasChanged = true
})
3. Getters
Getters 是 store 的计算属性:
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2,
// 使用其他 getter
doubleCountPlusOne(): number {
return this.doubleCount + 1
}
}
})
4. Actions
Actions 相当于组件中的 methods,用于封装业务逻辑:
export const useUserStore = defineStore('user', {
actions: {
async loadUser(id: number) {
try {
this.user = await api.getUser(id)
} catch (error) {
console.error('Failed to load user', error)
}
},
// 可以调用其他 action
async resetAndLoadUser(id: number) {
this.$reset()
await this.loadUser(id)
}
}
})
三、Pinia 高级用法
1. 插件系统
Pinia 支持插件扩展功能:
// 定义一个插件
function persistPlugin(context) {
const key = `pinia-${context.store.$id}`
// 从 localStorage 恢复状态
const savedState = localStorage.getItem(key)
if (savedState) {
context.store.$patch(JSON.parse(savedState))
}
// 订阅状态变化
context.store.$subscribe((mutation, state) => {
localStorage.setItem(key, JSON.stringify(state))
})
}
// 使用插件
const pinia = createPinia()
pinia.use(persistPlugin)
2. 服务端渲染 (SSR)
Pinia 对 SSR 有良好支持:
// 在服务器端
export default {
async setup() {
const pinia = createPinia()
const app = createApp(App).use(pinia)
// 执行数据预取逻辑
const userStore = useUserStore(pinia)
await userStore.loadUser()
return { app, pinia }
}
}
// 在客户端
const pinia = createPinia()
const app = createApp(App)
// 如果是在 SSR 场景下,注入服务器状态
if (window.__INITIAL_STATE__) {
pinia.state.value = window.__INITIAL_STATE__
}
app.use(pinia)
3. 测试 Store
测试 Pinia store 非常直接:
import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from './counter'
describe('Counter Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('increments count', () => {
const counter = useCounterStore()
expect(counter.count).toBe(0)
counter.increment()
expect(counter.count).toBe(1)
})
it('doubles count', () => {
const counter = useCounterStore()
counter.count = 5
expect(counter.doubleCount).toBe(10)
})
})
四、Pinia 与 Vuex 对比
特性 | Pinia | Vuex |
---|---|---|
Vue 3 支持 | ✅ | ✅ |
TypeScript 支持 | 优秀 | 一般 |
学习曲线 | 低 | 中 |
模块系统 | 扁平 | 嵌套 |
代码组织 | 灵活 | 严格 |
插件系统 | ✅ | ✅ |
开发工具支持 | ✅ | ✅ |
大小 (min+gzip) | ~1KB | ~10KB |
五、最佳实践
- 命名规范:使用
useXxxStore
命名 store,与组合式函数风格一致 - 模块化:按功能而非数据类型组织 store
- 避免过度使用:不是所有状态都需要放入 store,组件局部状态优先
- 组合 store:使用
store.$onAction
和store.$subscribe
实现 store 间通信 - TypeScript:充分利用类型推断,定义清晰的接口
六、常见问题解答
Q: 如何在组件外使用 store?
A: 在 setup 外部使用 store 需要传递 pinia 实例:
import { useUserStore } from './stores/user'
import { pinia } from './main'
const userStore = useUserStore(pinia)
Q: Pinia 支持时间旅行调试吗?
A: 是的,通过 Vue DevTools 可以支持时间旅行调试。
Q: 如何实现持久化?
A: 可以使用插件如 pinia-plugin-persistedstate
或自定义插件实现。
七、总结
Pinia 作为 Vue 3 的下一代状态管理解决方案,提供了更简洁的 API 和更好的开发体验。它减少了 Vuex 中的冗余概念,同时保留了核心功能,是 Vue 3 项目中状态管理的绝佳选择。无论是小型项目还是大型企业应用,Pinia 都能提供良好的可扩展性和维护性。