Pinia 是 Vue 的专属状态管理库,它被称为"下一代 Vuex"。相比于 Vuex,Pinia 在 Vue 3 中更简单、更轻量,且对 TypeScript 支持极好,完全摒弃了 Vuex 中繁琐的 mutations。
以下是 Pinia 在 Vue 3 中的完整使用流程,包含 定义 store 、在组件中使用 以及 State、Getter、Action 的详细操作。
1. 安装与挂载
首先需要在项目中安装 Pinia,并在 main.js 中挂载。
安装:
bash
npm install pinia
# 或
yarn add pinia
挂载:
javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 引入 createPinia
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia) // 挂载到 Vue 应用
app.mount('#app')
2. 定义 Store
推荐在 src/stores 目录下创建文件(如 user.js 或 user.ts)。Pinia 使用 defineStore 定义 Store,它接收两个参数:唯一 ID 和 配置对象 。
配置对象支持两种写法:Options API 风格 (类似 Vuex)和 Setup API 风格 (推荐,更符合 Vue 3 习惯)。
示例文件:src/stores/user.js
javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// 写法一:Setup Store (推荐 ⭐)
// 类似于 Vue 3 的 setup() 函数,ref 就是 state,computed 就是 getter,function 就是 action
export const useUserStore = defineStore('user', () => {
// ---------- State ----------
const count = ref(0)
const userName = ref('张三')
const userList = ref([])
// ---------- Getter ----------
// 类似于计算属性
const doubleCount = computed(() => count.value * 2)
// ---------- Action ----------
// 异步操作或同步逻辑都在这里,不再区分 mutations 和 actions
function increment() {
count.value++
}
async function fetchUserList() {
// 模拟异步请求
const res = await new Promise(resolve => {
setTimeout(() => resolve(['李四', '王五']), 1000)
})
userList.value = res
}
// 必须返回所有你想暴露的属性和方法
return { count, userName, userList, doubleCount, increment, fetchUserList }
})
3. 在组件中使用
在组件中导入定义好的 Store 函数并执行,即可获取 Store 实例。
场景 A:读取与展示 State/Getter
html
<template>
<div>
<p>用户名:{{ userStore.userName }}</p>
<p>双倍计数:{{ userStore.doubleCount }}</p>
<!-- 推荐使用 storeToRefs 解构,保持响应式 -->
<p>解构后的计数:{{ count }}</p>
</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia' // 官方提供的解构工具
const userStore = useUserStore()
// ⚠️ 注意:直接解构 userStore 会丢失响应式!
// const { userName } = userStore // ❌ 错误:userName 不再是响应式的
// ✅ 正确:使用 storeToRefs 解构 State 和 Getter
const { userName, count, doubleCount } = storeToRefs(userStore)
</script>
场景 B:修改 State 与调用 Action
修改数据非常灵活,可以直接赋值,也可以调用 Action。
html
<template>
<button @click="handleChange">修改数据</button>
<button @click="handleAction">调用方法</button>
<button @click="resetStore">重置状态</button>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const handleChange = () => {
// 方式 1:直接修改 (Pinia 允许直接修改,不像 Vuex 需要 mutations)
userStore.count = 10
// 方式 2:$patch 批量修改 (性能更好)
userStore.$patch({
count: 20,
userName: '李四'
})
// 方式 3:$patch 函数式修改 (适合复杂逻辑)
userStore.$patch((state) => {
state.count++
state.userName = state.userName + '1'
})
}
const handleAction = () => {
// 调用 Store 中的 Action
userStore.increment()
userStore.fetchUserList()
}
const resetStore = () => {
// 方式 4:重置 State 到初始状态
userStore.$reset()
}
</script>
4. 进阶技巧
1. 解构陷阱与 storeToRefs
这是新手最容易踩的坑。
-
State/Getter :如果直接解构(
const { count } = store),数据会失去响应式。必须使用storeToRefs。 -
Action :方法可以直接解构,不需要
storeToRefs。javascript// ✅ Action 直接解构完全没问题 const { increment, fetchUserList } = userStore
2. 修改 State 的三种方式对比
| 方式 | 代码示例 | 适用场景 |
|---|---|---|
| 直接赋值 | store.count++ |
简单、逻辑少的修改 |
| $patch (对象) | store.$patch({ count: 1, name: 'a' }) |
同时修改多个属性,性能稍好 |
| $patch (函数) | store.$patch(s => s.count++) |
适合修改逻辑比较复杂的情况 |
3. 持久化存储
Pinia 默认数据存放在内存中,刷新页面会丢失。如需持久化(如存入 localStorage),通常搭配插件 pinia-plugin-persistedstate。
javascript
// 安装后配置
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
// 在 defineStore 中开启
defineStore('user', () => { ... }, {
persist: true // 开启持久化
})
5. Pinia vs Vuex 总结
| 特性 | Vuex | Pinia |
|---|---|---|
| 修改数据 | 必须通过 mutations (同步) 和 actions (异步) |
移除 mutations,直接修改或统一用 actions |
| TypeScript | 支持较弱,需要大量类型声明 | 原生支持,类型推断完美 |
| 模块化 | 需要创建 modules,嵌套复杂 | 每个 Store 独立,天然模块化,无嵌套 |
| 体积 | 较大 | 极轻量 (约 1KB) |
| 一句话总结:在 Vue 3 中,Pinia 几乎是状态管理的唯一首选,它比 Vuex 更简单、更强大。 |