一、前言:为什么选择 Pinia?
在 Vue2 时代,我们常用 Vuex 来做全局状态管理。
但是 Vue3 带来了全新的响应式系统(Composition API + Proxy),于是 Vue 官方团队推出了 Pinia ------ 一款更轻量、更现代、更易用的状态管理库。
Pinia 的核心理念是:
"让状态管理像使用普通变量一样简单。"
相比 Vuex,它具备以下优势:
特点 | Vuex | Pinia |
---|---|---|
语法 | 基于 Mutations/Actions | 直接使用函数 |
类型推导 | 较弱 | TypeScript 支持友好 |
响应式实现 | Object.defineProperty | Vue3 Proxy |
模块化 | 需手动命名空间 | 天然支持模块化 |
体积 | 较大 | 小巧轻量(约1KB) |
二、Pinia 的核心原理
要理解 Pinia,先要知道它是如何在底层维持响应式的。
1. 状态的本质:Reactive
Pinia 使用 Vue3 的 reactive() 来存储状态。
每个 store 内部其实就是一个被 reactive 包裹的对象。
javascript
import { reactive } from 'vue'
const state = reactive({
count: 0
})
当 state.count++ 改变时,所有引用它的组件都会自动更新。
这就是 Pinia 响应式的根本机制。
2. Getter 的本质:Computed
在 Pinia 中,getter 相当于 Vue 中的 computed。
css
getters: {
doubleCount: (state) => state.count * 2
}
Pinia 会在内部把它转成一个 计算属性,只有依赖变化时才会重新计算。
因此它是 响应式、缓存式 的。
3. Action 的本质:普通函数 + 作用域代理
Pinia 不再强制使用 Mutation。
Action 其实就是对状态修改的封装函数。
javascript
actions: {
increment() {
this.count++
}
}
Pinia 通过 Proxy 让 this 指向 store 实例,因此你可以像访问普通对象一样修改状态。
4. 模块与依赖收集机制
每一个 store 都是一个独立的响应式作用域(Reactive Scope)。
Pinia 会将它注册到全局的 store 容器中(类似一个 Map 结构),并在组件使用时完成依赖收集。
组件一旦引用某个 store 的状态,Pinia 就会追踪它的依赖关系。
当 store 内的状态改变时,Pinia 会自动触发依赖更新 ------ 这就是响应式传播的原理。
三、Pinia 的安装与配置
1️⃣ 安装
csharp
npm install pinia
# 或者
yarn add pinia
2️⃣ 创建与挂载
在 main.js 中引入 Pinia 并挂载到 Vue 应用:
javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
四、定义第一个 Store
Pinia 推荐使用 defineStore() 来定义一个 store。
javascript
// src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 1. state:存放共享数据
state: () => ({
count: 0,
name: 'Pinia示例'
}),
// 2. getters:相当于计算属性
getters: {
doubleCount: (state) => state.count * 2
},
// 3. actions:用于定义修改逻辑
actions: {
increment() {
this.count++
},
setCount(val) {
this.count = val
}
}
})
五、在组件中使用 Store
在组件中使用时,就像普通变量一样简单。
xml
<template>
<div>
<h2>{{ counter.name }}</h2>
<p>当前数量:{{ counter.count }}</p>
<p>双倍数量:{{ counter.doubleCount }}</p>
<button @click="counter.increment">增加</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>
💡 响应式自动生效:
当 counter.count 改变时,界面会自动更新,无需手动刷新。
六、解构与响应式陷阱
Pinia store 是响应式对象,如果你用结构赋值要注意保持响应性。
错误写法 ❌:
scss
const { count } = useCounterStore()
console.log(count) // 不会响应更新
正确写法 ✅:
javascript
import { storeToRefs } from 'pinia'
const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store)
storeToRefs() 会帮你保留响应式引用。
七、模块化管理多个 Store
Pinia 天然支持多 store,无需命名空间:
javascript
// user.js
export const useUserStore = defineStore('user', {
state: () => ({
name: '张三',
token: ''
}),
actions: {
login(name) {
this.name = name
this.token = 'token123'
}
}
})
在组件中可以自由组合使用:
javascript
import { useUserStore } from '@/stores/user'
import { useCounterStore } from '@/stores/counter'
const user = useUserStore()
const counter = useCounterStore()
八、持久化存储(localStorage)
Pinia 本身不带持久化功能,但可以通过插件轻松实现:
✅ 手动持久化:
javascript
watch(
() => store.count,
(newVal) => {
localStorage.setItem('count', newVal)
}
)
✅ 使用插件(推荐):
安装:
css
npm i pinia-plugin-persistedstate
注册插件:
javascript
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
开启持久化:
javascript
export const useUserStore = defineStore('user', {
state: () => ({ name: '', token: '' }),
persist: true
})
九、Pinia + TypeScript 实践(简要示例)
Pinia 的类型推导非常强大。
在 TS 项目中可直接推断出 state 和 getter 的类型。
typescript
export const useTodoStore = defineStore('todo', {
state: () => ({
list: [] as string[]
}),
actions: {
addTodo(item: string) {
this.list.push(item)
}
}
})
// 自动推导类型
const store = useTodoStore()
store.addTodo('学习 Pinia')
十、Pinia 内部运行机制简述
Pinia 内部核心模块包括:
-
Store 实例注册
每个 defineStore() 都注册到全局 pinia._s 容器中。
-
Reactive 封装
使用 reactive() 包装 state,配合 Vue 的 effect 机制实现依赖收集。
-
Getter 包装为 computed
保证 getter 的懒计算与缓存特性。
-
Action 包装代理
使用 Proxy 代理 this 指向当前 store,并自动注入 devtools 日志。
-
订阅机制
Pinia 提供 store.$subscribe(),可以监听 state 变化。
十一、实战示例:Todo 应用
xml
<template>
<div>
<h2>我的待办事项</h2>
<input v-model="newTask" @keyup.enter="addTodo" placeholder="输入任务..."/>
<ul>
<li v-for="(item, index) in todos.list" :key="index">
{{ item }}
<button @click="removeTodo(index)">删除</button>
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useTodoStore } from '@/stores/todo'
const todos = useTodoStore()
const newTask = ref('')
const addTodo = () => {
if (newTask.value.trim()) {
todos.addTodo(newTask.value)
newTask.value = ''
}
}
const removeTodo = (i) => todos.list.splice(i, 1)
</script>
十二、总结
内容 | 关键点 |
---|---|
状态管理核心 | Vue3 的 reactive 与 computed |
改进点 | 无需 mutation、天然模块化 |
类型支持 | 友好且强大 |
响应式机制 | Proxy + Effect 依赖追踪 |
持久化 | 插件 pinia-plugin-persistedstate |
适用场景 | 中大型 Vue3 项目,全局状态同步 |
Pinia 的设计哲学是:
"简单到你几乎忘了自己在用状态管理库。"