引言
在 Vue 生态系统的发展进程中,状态管理方案经历了从 Vuex 到 Pinia 的重要演变。Pinia(发音为 /piːnjə/,类似于英语中的 "pineapple")是 Vue 3 的官方推荐状态管理库,由 Vue 核心团队成员 Posva 开发。它旨在提供比 Vuex 更简洁、更类型安全、更模块化的状态管理体验。
为什么选择 Pinia?
1. 更简洁的 API 设计
Pinia 去除了 Vuex 中的 mutations 概念,只保留 state、getters 和 actions 三个核心概念,使代码更加直观和易于理解。
2. 完美的 TypeScript 支持
Pinia 从一开始就为 TypeScript 设计,无需复杂的配置即可享受完整的类型推断和类型检查功能。
3. 模块化架构
每个 store 都是独立的模块,可以按需导入和使用,避免了 Vuex 中复杂的命名空间配置。
4. 轻量级
Pinia 的包体积非常小(约 1KB gzipped),对应用性能影响极小。
5. Vue 3 Composition API 友好
Pinia 与 Vue 3 的 Composition API 完美集成,同时也支持 Options API。
核心概念
State(状态)
State 是 store 中的数据源,类似于组件中的 data。
javascript
1// stores/counter.js
2import { defineStore } from 'pinia'
3
4export const useCounterStore = defineStore('counter', {
5 state: () => ({
6 count: 0,
7 name: 'Pinia Store'
8 })
9})
Getters(计算属性)
Getters 相当于 store 的计算属性,用于派生状态。
javascript
1export const useCounterStore = defineStore('counter', {
2 state: () => ({
3 count: 0,
4 price: 10
5 }),
6 getters: {
7 doubleCount: (state) => state.count * 2,
8 totalPrice() {
9 return this.count * this.price
10 }
11 }
12})
Actions(操作)
Actions 用于处理业务逻辑,可以是同步或异步操作。
javascript
1export const useCounterStore = defineStore('counter', {
2 state: () => ({
3 count: 0
4 }),
5 actions: {
6 increment() {
7 this.count++
8 },
9 async fetchCount() {
10 const response = await fetch('/api/count')
11 this.count = await response.json()
12 }
13 }
14})
安装与配置
安装
csharp
1npm install pinia
2# 或
3yarn add pinia
4# 或
5pnpm add pinia
基本配置
javascript
1// main.js
2import { createApp } from 'vue'
3import { createPinia } from 'pinia'
4import App from './App.vue'
5
6const app = createApp(App)
7const pinia = createPinia()
8
9app.use(pinia)
10app.mount('#app')
在组件中使用
Composition API 方式
xml
1<script setup>
2import { useCounterStore } from '@/stores/counter'
3import { computed, ref } from 'vue'
4
5const counterStore = useCounterStore()
6
7// 直接访问 state
8console.log(counterStore.count)
9
10// 调用 actions
11counterStore.increment()
12
13// 使用 getters
14const doubleCount = computed(() => counterStore.doubleCount)
15
16// 响应式解构(推荐方式)
17import { storeToRefs } from 'pinia'
18const { count, name } = storeToRefs(counterStore)
19const { increment, fetchCount } = counterStore
20</script>
21
22<template>
23 <div>
24 <p>计数: {{ count }}</p>
25 <p>名称: {{ name }}</p>
26 <p>双倍计数: {{ doubleCount }}</p>
27 <button @click="increment">增加</button>
28 </div>
29</template>
Options API 方式
xml
1<script>
2import { useCounterStore } from '@/stores/counter'
3import { mapState, mapActions, mapGetters } from 'pinia'
4
5export default {
6 computed: {
7 ...mapState(useCounterStore, ['count', 'name']),
8 ...mapGetters(useCounterStore, ['doubleCount'])
9 },
10 methods: {
11 ...mapActions(useCounterStore, ['increment', 'fetchCount'])
12 }
13}
14</script>
高级特性
持久化存储
通过插件实现状态持久化:
1npm install pinia-plugin-persistedstate
javascript
1// main.js
2import { createPinia } from 'pinia'
3import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
4
5const pinia = createPinia()
6pinia.use(piniaPluginPersistedstate)
7
8// stores/user.js
9export const useUserStore = defineStore('user', {
10 state: () => ({
11 token: '',
12 userInfo: null
13 }),
14 persist: {
15 key: 'my_user_store',
16 storage: localStorage,
17 paths: ['token'] // 只持久化 token
18 }
19})
商店组合
可以在一个 store 中使用其他 store:
javascript
1export const useCartStore = defineStore('cart', {
2 state: () => ({
3 items: []
4 }),
5 getters: {
6 total() {
7 const productStore = useProductStore()
8 return this.items.reduce((sum, item) => {
9 const product = productStore.products.find(p => p.id === item.productId)
10 return sum + (product?.price || 0) * item.quantity
11 }, 0)
12 }
13 }
14})
订阅和监听
javascript
1const counterStore = useCounterStore()
2
3// 订阅任何状态变化
4counterStore.$subscribe((mutation, state) => {
5 console.log('状态变化:', mutation)
6 console.log('新状态:', state)
7})
8
9// 监听特定 action
10counterStore.$onAction(({ name, args, after, onError }) => {
11 after(() => {
12 console.log(`${name} 执行完成`)
13 })
14 onError((error) => {
15 console.error(`${name} 执行失败:`, error)
16 })
17})
Pinia vs Vuex
| 特性 | Pinia | Vuex |
|---|---|---|
| Mutations | ❌ 不需要 | ✅ 必需 |
| TypeScript 支持 | 🌟 优秀 | ⚠️ 需要额外配置 |
| 模块化 | 🌟 原生支持 | ⚠️ 需要命名空间 |
| 包体积 | ~1KB | ~3KB |
| DevTools 支持 | ✅ 完整支持 | ✅ 完整支持 |
| Vue 3 支持 | 🌟 首选 | ⚠️ 兼容但非首选 |
| 热重载 | ✅ 支持 | ✅ 支持 |
最佳实践
1. 按功能模块组织 stores
1stores/
2├── user.js
3├── cart.js
4├── products.js
5└── settings.js
2. 使用 TypeScript 获得最佳体验
typescript
1// stores/counter.ts
2import { defineStore } from 'pinia'
3
4interface CounterState {
5 count: number
6 name: string
7}
8
9export const useCounterStore = defineStore('counter', {
10 state: (): CounterState => ({
11 count: 0,
12 name: 'Pinia'
13 }),
14 getters: {
15 doubleCount: (state): number => state.count * 2
16 },
17 actions: {
18 increment(): void {
19 this.count++
20 }
21 }
22})
3. 避免直接修改 state
始终通过 actions 来修改状态,以保持代码的可预测性和可维护性。
4. 合理使用 persist 插件
只对需要持久化的数据进行配置,避免不必要的存储开销。
总结
Pinia 作为 Vue 3 的官方状态管理解决方案,以其简洁的 API、出色的 TypeScript 支持和现代化的设计理念,成为了 Vue 开发者管理应用状态的首选工具。它不仅解决了 Vuex 中存在的一些痛点,还提供了更好的开发体验和更强大的功能扩展能力。
对于新的 Vue 3 项目,强烈推荐使用 Pinia 进行状态管理。对于现有的 Vuex 项目,也可以考虑逐步迁移到 Pinia,以享受更现代的开发体验。
随着 Vue 生态系统的不断发展,Pinia 也在持续演进,未来将会提供更多实用的功能和优化,成为 Vue 开发者不可或缺的工具之一。