Pinia 是 Vue 官方推荐的状态管理库,替代了传统的 Vuex,专为 Vue3 设计,支持 Composition API 和 TypeScript,API 更简洁,是目前 Vue 项目状态管理的最佳选择。
一、为什么选择 Pinia?
与 Vuex 相比,Pinia 具有以下优势:
- 无需
mutations
,直接在actions
中修改状态 - 完美支持 Vue3 的 Composition API
- 内置 TypeScript 支持,类型推断更友好
- 简化的 API,学习成本更低
- 支持多个 Store 实例,无需嵌套模块
- 更轻量(约 1KB)
二、安装 Pinia
在 Vue3 项目中安装 Pinia:
npm install pinia --save
# 或
yarn add pinia
三、初始化 Pinia
首先需要在项目入口文件中创建 Pinia 实例并挂载到 Vue 应用(main.js):
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia' // 导入 createPinia// 创建 Pinia 实例
const pinia = createPinia()// 创建应用并使用 Pinia
createApp(App).use(pinia) // 挂载 Pinia.mount('#app')
四、核心概念与基础使用
1. 创建第一个 Store
Store 是 Pinia 的核心,用于存储全局状态。使用 defineStore()
创建 Store,它接收两个参数:
- 第一个参数:Store 的唯一 ID(字符串)
- 第二个参数:配置对象(包含
state
、getters
、actions
)
import { defineStore } from 'pinia'// 定义并导出 Store
export const useCounterStore = defineStore('counter', {// 状态:存储数据的地方state: () => ({count: 0,name: '计数器'}),// 计算属性:基于 state 派生的数据getters: {// 基本使用doubleCount: (state) => state.count * 2,// 使用其他 getters(通过 this)doubleCountPlusOne() {return this.doubleCount + 1},// 带参数的 getter(返回函数)getCountWithAdd: (state) => (num) => state.count + num},// 方法:处理业务逻辑(支持同步和异步)actions: {increment() {// 直接修改 state(无需 mutations)this.count++},decrement() {this.count--},// 带参数的 actionincrementBy(num) {this.count += num},// 异步 action(如请求 API)async fetchData() {const res = await fetch('https://api.example.com/count')const data = await res.json()this.count = data.count}}
})
2. 在组件中使用 Store
在组件中通过创建的 useCounterStore
函数获取 Store 实例,然后访问其 state
、getters
和 actions
。
(1)使用 Composition API(推荐)
<template><div class="counter"><h2>{{ counterStore.name }}</h2><p>当前计数:{{ counterStore.count }}</p><p>计数翻倍:{{ counterStore.doubleCount }}</p><p>计数翻倍+1:{{ counterStore.doubleCountPlusOne }}</p><p>计数+5:{{ counterStore.getCountWithAdd(5) }}</p><button @click="counterStore.increment">+1</button><button @click="counterStore.decrement">-1</button><button @click="counterStore.incrementBy(10)">+10</button><button @click="counterStore.fetchData">从API获取数据</button></div>
</template><script setup>
// 导入 Store
import { useCounterStore } from '../stores/counterStore'// 获取 Store 实例(注意:不要解构,会失去响应性)
const counterStore = useCounterStore()
</script>
(2)使用 Options API
如果使用 Options API,可以通过 mapState
、mapGetters
、mapActions
辅助函数映射 Store:
<template><div><p>计数:{{ count }}</p><p>翻倍:{{ doubleCount }}</p><button @click="increment">+1</button></div>
</template><script>
import { useCounterStore } from '../stores/counterStore'
import { mapState, mapGetters, mapActions } from 'pinia'export default {computed: {// 映射 state...mapState(useCounterStore, ['count', 'name']),// 映射 getters...mapGetters(useCounterStore, ['doubleCount'])},methods: {// 映射 actions...mapActions(useCounterStore, ['increment', 'decrement'])}
}
</script>
五、修改 State 的多种方式
Pinia 允许通过多种方式修改 state,比 Vuex 更灵活:
1. 直接修改(最简单)
const counterStore = useCounterStore()
counterStore.count++ // 直接修改
counterStore.name = '新计数器' // 直接修改
2. 使用 $patch
批量修改
适合同时修改多个状态:
// 对象形式
counterStore.$patch({count: 10,name: '批量修改后的计数器'
})// 函数形式(更灵活,支持复杂逻辑)
counterStore.$patch((state) => {state.count += 5state.name = '函数修改的计数器'
})
3. 通过 actions
修改
适合包含业务逻辑的修改(推荐):
// 在 Store 中定义 action
actions: {resetAndSet(num) {this.count = 0 // 重置this.count += num // 再修改this.name = '通过action修改'}
}// 在组件中调用
counterStore.resetAndSet(10)
4. 替换整个 State
使用 $state
替换整个状态对象:
counterStore.$state = {count: 100,name: '全新状态'
}
六、异步 Actions 详解
Pinia 的 actions
支持异步操作(如 API 请求),无需像 Vuex 那样区分 actions
和 mutations
:
import { defineStore } from 'pinia'
import axios from 'axios'export const useUserStore = defineStore('user', {state: () => ({userInfo: null,loading: false,error: null}),actions: {// 登录(异步操作)async login(username, password) {try {this.loading = truethis.error = null// 调用 APIconst res = await axios.post('/api/login', {username,password})// 存储用户信息this.userInfo = res.data.user// 保存 token 到本地存储localStorage.setItem('token', res.data.token)return res.data // 返回结果给组件} catch (err) {this.error = err.response?.data?.message || '登录失败'throw err // 抛出错误,让组件可以捕获} finally {this.loading = false // 无论成功失败,都结束加载}},// 退出登录logout() {this.userInfo = nulllocalStorage.removeItem('token')},// 获取用户信息async fetchUserInfo() {try {this.loading = trueconst token = localStorage.getItem('token')const res = await axios.get('/api/user', {headers: { Authorization: `Bearer ${token}` }})this.userInfo = res.data} catch (err) {this.error = '获取用户信息失败'} finally {this.loading = false}}}
})
在组件中使用异步 action:
<script setup>
import { useUserStore } from '../stores/userStore'const userStore = useUserStore()const handleLogin = async () => {try {await userStore.login('admin', '123456')alert('登录成功')} catch (err) {alert(userStore.error) // 显示错误信息}
}
</script>
七、Store 组合(跨 Store 调用)
在大型项目中,可能需要多个 Store 协同工作,Pinia 支持在一个 Store 中直接使用另一个 Store:
import { defineStore } from 'pinia'
import { useUserStore } from './userStore' // 导入其他 Storeexport const useCartStore = defineStore('cart', {state: () => ({items: []}),getters: {totalPrice: (state) => {return state.items.reduce((total, item) => total + item.price * item.quantity, 0)},// 结合用户信息计算折扣价discountedPrice() {const userStore = useUserStore() // 获取用户 Store// 会员打9折return userStore.userInfo?.isVip ? this.totalPrice * 0.9 : this.totalPrice}},actions: {addItem(product) {const userStore = useUserStore()// 未登录不能添加到购物车if (!userStore.userInfo) {throw new Error('请先登录')}this.items.push({ ...product, quantity: 1 })}}
})
八、持久化存储(状态持久化)
Pinia 本身不提供持久化功能,但可以通过 pinia-plugin-persistedstate
插件实现状态持久化(刷新页面后状态不丢失):
1. 安装插件
npm install pinia-plugin-persistedstate --save
2. 配置插件
// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 导入插件
import App from './App.vue'const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) // 使用插件createApp(App).use(pinia).mount('#app')
3. 在 Store 中启用持久化
export const useUserStore = defineStore('user', {state: () => ({ /* ... */ }),// 启用持久化persist: true, // 默认为 localStorage 存储整个 state// 或自定义配置persist: {key: 'user-storage', // 存储的键名storage: sessionStorage, // 使用 sessionStoragepaths: ['userInfo'] // 只持久化 userInfo 字段}
})
九、最佳实践
- 按功能拆分 Store:避免创建单一的大 Store,按功能(如
userStore
、cartStore
、productStore
)拆分 - 使用 Composition API:在组件中优先使用
setup()
语法,更符合 Vue3 生态 - 复杂逻辑放 actions:组件只负责调用 actions,业务逻辑封装在 Store 中
- 避免过度使用全局状态:只有多个组件共享的数据才需要放入 Store,组件内部数据用
ref
/reactive
- TypeScript 类型定义:为 state 和 actions 参数添加类型,提升开发体验
总结
Pinia 是 Vue3 状态管理的最佳选择,其简洁的 API 和对 Composition API 的完美支持,让状态管理变得简单直观。通过本教程,你已掌握:
- Pinia 的安装与初始化
- Store 的创建(state、getters、actions)
- 状态修改的多种方式
- 异步 actions 的实现
- Store 组合与持久化存储