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