Pinia 是 Vue.js 官方推荐的状态管理库(自 Vue 3 起),用于替代 Vuex。它具有更简洁的 API、更好的 TypeScript 支持、模块化设计以及对 Composition API 的天然适配。
一、Pinia 简介
- 作者:Eduardo San Martin Morote(Vue 团队核心成员)
- 特点 :
- 无 mutations,只有 state、getters、actions
- 支持 Vue 2(≥2.6.14)和 Vue 3
- 完美支持 TypeScript(无需额外配置)
- 支持 Vue Devtools 调试
- 支持服务端渲染(SSR)
- 模块自动注册,无需手动嵌套
- 轻量(gzip 后约 1KB)
二、安装与基本使用
- 安装
bash
# Vue 3 项目
npm install pinia
# Vue 2 项目(需额外安装 composition-api)
npm install pinia @vue/composition-api
- 在 Vue 应用中注册
javascript
// main.js (Vue 3)
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')
注意:Vue 2 中使用 new Vue({ pinia }) 语法。
三、定义 Store
使用 defineStore 创建 store,接受两个参数:id 和 配置对象(或函数)。
- 选项式写法(类似 Vuex)
javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Eduardo'
}),
getters: {
doubleCount: (state) => state.count * 2,
// 带参数的 getter(使用箭头函数返回函数)
doubleCountPlus: (state) => (n) => state.count * 2 + n
},
actions: {
increment() {
this.count++
},
reset() {
this.count = 0
},
// 支持异步
async fetchData() {
const res = await fetch('/api/data')
this.data = await res.json()
}
}
})
- 函数式写法(使用 Composition API)
javascript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
function reset() {
count.value = 0
}
return { count, name, doubleCount, increment, reset }
})
✅ 推荐函数式写法:更灵活、复用性强、便于单元测试。
四、在组件中使用 Store
javascript
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment">+</button>
<button @click="reset">Reset</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// 解构会丢失响应性!应使用 toRefs 或 storeToRefs
// ❌ 错误写法:
// const { count, increment } = counter
// ✅ 正确写法(推荐):
import { storeToRefs } from 'pinia'
const { count, doubleCount } = storeToRefs(counter)
const { increment } = counter
</script>
五、核心特性详解
- 自动响应性
State 是响应式的,修改会自动触发视图更新。
Getters 是缓存的 computed 属性。
- Actions
替代 mutations + actions。
可同步/异步。
可调用其他 store 的 actions。
javascript
// 调用其他 store
const useUserStore = defineStore('user', {
actions: {
async login() {
const counter = useCounterStore()
counter.increment()
}
}
})
- Store 间通信
直接 import 并调用其他 store。
无命名空间冲突(每个 store 有唯一 ID)。
- TypeScript 支持
开箱即用,无需额外配置。
类型推导精准。
javascript
interface User {
id: number
name: string
}
export const useUserStore = defineStore('user', () => {
const users = ref<User[]>([])
// 类型自动推导 ✅
})
六、插件系统(Plugin)
Pinia 支持插件扩展功能(类似 Vuex 的插件)。
示例:添加 localStorage 持久化
javascript
// plugins/persist.js
export const persistPlugin = ({ store }) => {
const key = `pinia:${store.$id}`
// 初始化时从 localStorage 读取
const saved = localStorage.getItem(key)
if (saved) {
store.$state = JSON.parse(saved)
}
// 监听状态变化并保存
store.$subscribe((mutation, state) => {
localStorage.setItem(key, JSON.stringify(state))
})
}
// main.js
import { createPinia } from 'pinia'
import { persistPlugin } from './plugins/persist'
const pinia = createPinia()
pinia.use(persistPlugin)
插件可扩展内容:
添加属性到 store(如 $db)
修改 $state
添加新选项(如 $reset)
七、服务端渲染(SSR)支持
Pinia 原生支持 SSR,只需在服务端创建新的 Pinia 实例:
javascript
// server-entry.js
import { createPinia } from 'pinia'
export function createApp() {
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
return { app, pinia }
}
自动处理 hydration,无需额外配置。
八、Devtools 调试
- 自动集成 Vue Devtools。
- 可追踪 actions 调用、状态变化。
- 时间旅行调试(time-travel debugging)。
九、与 Vuex 对比
| API 简洁性 | ✅ 更简洁(无 mutations) | ❌ 较复杂 |
| TypeScript | ✅ 原生支持 | ⚠️ 需额外配置 |
| 模块嵌套 | ✅ 扁平化(自动注册) | ❌ 需手动嵌套 |
| Composition API | ✅ 天然支持 | ❌ 需额外适配 |
| 包体积 | ✅ 更小 | ❌ 较大 |
| Vue 2 支持 | ✅(需插件) | ✅ |
十、最佳实践
- 每个 store 单文件 :
stores/user.js、stores/product.js - 使用函数式定义:便于逻辑复用和测试
- 避免直接修改 state:始终通过 actions
- 使用 storeToRefs 解构:保持响应性
- 合理拆分 store:按业务域划分,不要一个 store 管所有状态