什么是 Pinia?
Pinia 是 Vue.js 官方推荐的状态管理库,它取代了 Vuex 成为 Vue 3 应用的首选状态管理方案。相比 Vuex,Pinia 提供了更简洁、直观的 API,并且具有出色的 TypeScript 支持。
Pinia 的核心优势
- 轻量级:体积更小,性能更好
- 直观:API 设计更简单,学习成本低
- TypeScript 支持:原生支持 TypeScript,无需额外配置
- 开发工具集成:与 Vue DevTools 完美集成
- 热更新:支持模块热替换,提升开发体验
Pinia 核心概念
1. Store(存储)
Store 是 Pinia 中保存状态的地方,它是使用 defineStore() 函数创建的。
import { defineStore } from 'pinia'
// 创建名为 counter 的 store
export const useCounterStore = defineStore('counter', {
// store 的实现
})
2. State(状态)
State 是存储数据的地方,相当于组件中的 data。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Eduardo'
})
})
3. Getters(计算属性)
Getters 相当于组件中的 computed,用于计算派生状态。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne(): number {
return this.doubleCount + 1
}
}
})
4. Actions(操作)
Actions 相当于组件中的 methods,用于处理业务逻辑。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
},
async fetchData() {
const response = await api.getData()
this.data = response.data
}
}
})
Pinia 在实际项目中的应用
1. 安装和配置
npm install pinia
在 main.js 中安装 Pinia:
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')
2. 创建 Store
让我们以一个酒店管理系统为例,创建一个存储订单相关数据的 store:
// stores/commonLists.js
import { defineStore } from 'pinia'
export const useCommonListsStore = defineStore('commonLists', {
// 状态
state: () => ({
orderSourceList: [],
orderCustomerList: [],
loading: {
orderSources: false,
orderCustomers: false
}
}),
// 计算属性
getters: {
getOrderSourceList: (state) => state.orderSourceList,
getOrderCustomerList: (state) => state.orderCustomerList,
isLoadingOrderSources: (state) => state.loading.orderSources,
isLoadingOrderCustomers: (state) => state.loading.orderCustomers
},
// 操作方法
actions: {
// 获取订单来源列表
async fetchOrderSources() {
if (this.orderSourceList.length > 0) {
return { success: true, data: this.orderSourceList }
}
this.loading.orderSources = true
try {
// 模拟 API 请求
const response = await getAllOrderSources()
if (response && response.records) {
this.orderSourceList = response.records.map(item => ({
value: item.id,
label: item.name
}))
return { success: true, data: this.orderSourceList }
}
} catch (error) {
console.error('获取订单来源失败:', error)
return { success: false, message: error.message }
} finally {
this.loading.orderSources = false
}
}
}
})
3. 在组件中使用 Store
基本使用方式
<script setup>
import { useCommonListsStore } from '@/stores/commonLists'
const commonListsStore = useCommonListsStore()
// 访问状态
console.log(commonListsStore.orderSourceList)
// 调用 action
await commonListsStore.fetchOrderSources()
</script>
在模板中使用
<template>
<div>
<select v-model="selectedSource">
<option
v-for="source in commonListsStore.orderSourceList"
:key="source.value"
:value="source.value"
>
{{ source.label }}
</option>
</select>
<div v-if="commonListsStore.isLoadingOrderSources">
正在加载...
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useCommonListsStore } from '@/stores/commonLists'
const commonListsStore = useCommonListsStore()
const selectedSource = ref('')
// 组件挂载时加载数据
commonListsStore.fetchOrderSources()
</script>
Pinia 高级特性
1. Store 之间的相互依赖
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
age: 0
})
})
export const useMainStore = defineStore('main', {
state: () => ({
count: 99
}),
getters: {
// 使用其他 store 的状态
doubleCount: (state) => state.count * 2
},
actions: {
// 在 action 中使用其他 store
incrementWithUserAge() {
const userStore = useUserStore()
this.count += userStore.age
}
}
})
2. Store 持久化
使用 pinia-plugin-persistedstate 插件实现数据持久化:
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
// 在 store 中配置持久化
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
persist: true // 启用持久化
})
3. Store 的模块化
对于大型应用,建议按功能模块划分 store:
stores/
├── index.js # 导出所有 store
├── user.js # 用户相关状态
├── product.js # 产品相关状态
├── cart.js # 购物车状态
└── commonLists.js # 公共列表数据
最佳实践
1. Store 命名规范
- 使用
use前缀命名 store 函数 - Store 名称应反映其功能
- 避免 store 名称冲突
2. 状态管理原则
- 单一数据源:每个数据片段只应在一处定义
- 状态不可变性:直接修改 state,而不是通过 setter
- 操作集中化:复杂的业务逻辑放在 actions 中
3. 异步操作处理
actions: {
async fetchUserData(userId) {
this.loading = true
try {
const response = await api.getUserById(userId)
this.user = response.data
this.error = null
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
}
}
4. 错误处理
actions: {
async saveData(data) {
try {
await api.saveData(data)
this.savedSuccessfully = true
} catch (error) {
this.errorMessage = error.message
throw error // 重新抛出错误以便上层处理
}
}
}
与 Vuex 的对比
| 特性 | Pinia | Vuex |
|---|---|---|
| API 复杂度 | 简单直观 | 相对复杂 |
| TypeScript 支持 | 原生支持 | 需要额外配置 |
| 体积 | 更小 | 较大 |
| Vue DevTools 支持 | 更好 | 基础支持 |
| 学习成本 | 低 | 中等 |
总结
Pinia 作为 Vue 生态的新一代状态管理解决方案,以其简洁的 API、出色的 TypeScript 支持和现代化的设计理念,成为构建 Vue 应用的理想选择。通过合理使用 Pinia,我们可以构建出结构清晰、易于维护的状态管理架构,提升开发效率和应用质量。
在实际项目中,建议根据业务需求合理设计 store 结构,遵循单一职责原则,将相关联的状态组织在一起,同时注意避免 store 之间的过度耦合,保持良好的可维护性。