Hi,我是前端人类学 !
在 Vue 应用中,状态管理从"多组件共享数据"到"复杂业务逻辑协调",常常是项目架构中最具挑战性的一环。随着 Vue3 的普及,Pinia 已取代 Vuex 成为官方推荐的状态管理库。
本文将从前端状态管理的核心问题出发,通过完整的实战案例,对比 Vuex 与 Pinia 的差异,并深入讲解模块化设计与数据持久化的最佳实践。
文章目录
-
- [一、Vuex vs Pinia:为什么官方转向了 Pinia?](#一、Vuex vs Pinia:为什么官方转向了 Pinia?)
-
- [1.1 核心差异一览](#1.1 核心差异一览)
- [1.2 Pinia 的核心优势](#1.2 Pinia 的核心优势)
- [1.3 何时仍用 Vuex?](#1.3 何时仍用 Vuex?)
- [二、Pinia 完整实战:从零构建状态管理](#二、Pinia 完整实战:从零构建状态管理)
-
- [2.1 安装与初始化](#2.1 安装与初始化)
- [2.2 创建第一个 Store(Options Store 风格)](#2.2 创建第一个 Store(Options Store 风格))
- [2.3 在组件中使用](#2.3 在组件中使用)
- [2.4 Setup Store 风格(推荐)](#2.4 Setup Store 风格(推荐))
- 三、模块化设计:组织大型应用的状态
-
- [3.1 推荐的目录结构](#3.1 推荐的目录结构)
- [3.2 跨模块引用](#3.2 跨模块引用)
- [3.3 模块组合与统一导出](#3.3 模块组合与统一导出)
- 四、数据持久化:刷新不丢失
-
- [4.1 安装与配置](#4.1 安装与配置)
- [4.2 Store 级配置](#4.2 Store 级配置)
- [4.3 精细控制](#4.3 精细控制)
- [4.4 其他持久化方案](#4.4 其他持久化方案)
- 五、最佳实践总结
-
- [5.1 核心实践](#5.1 核心实践)
- [5.2 性能注意事项](#5.2 性能注意事项)
一、Vuex vs Pinia:为什么官方转向了 Pinia?
1.1 核心差异一览
| 对比维度 | Vuex | Pinia |
|---|---|---|
| 语法复杂度 | 需定义 state/mutations/actions/getters,样板代码多 | 直接使用 actions 修改 state,无需 mutations |
| TypeScript 支持 | 需手动声明类型,繁琐易错 | 自动类型推导,天然友好 |
| 模块化 | 需手动配置 modules 嵌套,存在命名空间问题 | 每个 store 独立,天然模块化 |
| 性能 | Object.defineProperty(Vue2)或 Proxy,中间件开销约 10-20% | 基于 Vue3 Proxy,更精准的依赖追踪,性能提升约 30-40% |
| 异步处理 | 需 action + mutation 组合,流程繁琐 | action 直接返回 Promise,优雅简洁 |
1.2 Pinia 的核心优势
语法极简 :在 Vuex 中定义一个计数器需要编写 state、mutations、actions 等多个部分;而 Pinia 只需一个 defineStore,在 actions 中直接修改 this.count 即可。
TypeScript 一等公民:Pinia 在设计之初就充分考虑了对 TypeScript 的支持,几乎所有 API 都能自动推导类型,大幅减少手动类型声明的工作量。
轻量且性能优秀:在同样执行 1000 次同步 action 的场景下,Pinia 的延迟约为 5-8ms,而 Vuex 为 8-12ms,提升约 30%。高频触发场景(1000次/秒)下,Pinia 的吞吐量更是优于 Vuex 约 40-50%。
1.3 何时仍用 Vuex?
- 项目为 Vue2 且已深度使用 Vuex,迁移成本过高
- 依赖较复杂的 Vuex 中间件生态,如
vuex-persistedstate的特殊用法 - 对 Vuex 的严格单向数据流规范有强依赖
对于新项目,尤其是 Vue3 项目,Pinia 无疑是更优的选择。
二、Pinia 完整实战:从零构建状态管理
2.1 安装与初始化
bash
pnpm add pinia
# 或 npm install pinia
在 main.ts 中注册 Pinia:
typescript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
2.2 创建第一个 Store(Options Store 风格)
typescript
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: '计数器'
}),
getters: {
// 自动推导返回类型
doubleCount: (state) => state.count * 2,
// 使用 this 访问其他 getter
formattedCount(): string {
return `${this.name}: ${this.count}`
}
},
actions: {
increment() {
this.count++
},
incrementBy(amount: number) {
this.count += amount
},
// 异步 action 直接返回 Promise
async fetchInitialCount() {
const res = await api.get('/count')
this.count = res.data
}
}
})
2.3 在组件中使用
html
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
// ✅ 使用 storeToRefs 解构 state 和 getters,保持响应性
const { count, doubleCount, formattedCount } = storeToRefs(counterStore)
// ❌ 直接解构会丢失响应性
// const { count, doubleCount } = counterStore
// ✅ actions 可以直接解构
const { increment, incrementBy } = counterStore
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<p>{{ formattedCount }}</p>
<button @click="increment()">+1</button>
<button @click="incrementBy(5)">+5</button>
</div>
</template>
2.4 Setup Store 风格(推荐)
Pinia 同时支持 Composition API 风格的 Store,更灵活且与 Vue3 生态融合更好:
typescript
// stores/user.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
// state
const userInfo = ref<UserInfo | null>(null)
const token = ref('')
// getters
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => userInfo.value?.username || '游客')
// actions
function setToken(newToken: string) {
token.value = newToken
}
async function login(credentials: LoginParams) {
const res = await api.login(credentials)
token.value = res.token
userInfo.value = res.user
}
function logout() {
token.value = ''
userInfo.value = null
}
return { userInfo, token, isLoggedIn, userName, setToken, login, logout }
})
三、模块化设计:组织大型应用的状态
随着应用规模增长,将状态按业务领域拆分是保持代码可维护性的关键。Pinia 天然支持模块化------每个 defineStore 定义的就是一个独立的模块。
3.1 推荐的目录结构
text
stores/
├── index.ts # Pinia 实例导出
├── modules/
│ ├── user.ts # 用户模块
│ ├── cart.ts # 购物车模块
│ ├── app.ts # 应用全局配置
│ └── permission.ts # 权限模块
└── types/
└── index.ts # 类型定义
3.2 跨模块引用
不同 Store 之间可以相互引用,只需在 action 中调用其他 Store 的实例:
typescript
// stores/modules/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as CartItem[]
}),
actions: {
async checkout() {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('请先登录')
}
// 执行下单逻辑...
}
}
})
3.3 模块组合与统一导出
在 stores/index.ts 中统一导出所有模块:
typescript
// stores/index.ts
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
export * from './modules/user'
export * from './modules/cart'
export * from './modules/app'
四、数据持久化:刷新不丢失
页面刷新后状态重置是单页应用最常见的问题之一。Pinia 本身不提供持久化能力,但可通过插件轻松实现。最成熟方案是 pinia-plugin-persistedstate,它受到 vuex-persistedstate 启发,API 简洁且功能完备。
4.1 安装与配置
bash
pnpm add pinia-plugin-persistedstate
在 main.ts 中注册:
typescript
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
4.2 Store 级配置
在需要持久化的 Store 中添加 persist 选项:
typescript
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
userInfo: null
}),
actions: { /* ... */ },
persist: {
key: 'user-storage', // 存储 key,默认使用 store id
storage: localStorage, // 默认 localStorage
pick: ['token', 'userInfo'] // 仅持久化指定字段
}
})
4.3 精细控制
typescript
// 使用 sessionStorage,且排除敏感字段
persist: {
storage: sessionStorage,
omit: ['tempData'], // 排除不需要持久化的字段
}
// 在 Setup Store 中使用
export const useCounterStore = defineStore(
'counter',
() => {
const count = ref(0)
return { count }
},
{
persist: true // 简单启用全部持久化
}
)
4.4 其他持久化方案
@erlihs/pinia-plugin-storage:支持 localStorage、sessionStorage、cookies、indexedDB 多适配器,并具备跨标签页实时同步、防抖、命名空间等高级特性pinia-plugin-persist-uni:专为 uni-app 设计的持久化方案,采用 uniAppStorage 存储
五、最佳实践总结
5.1 核心实践
| 实践 | 说明 |
|---|---|
| 模块化拆分 | 按业务领域拆分 Store,避免单一 Store 膨胀 |
使用 storeToRefs |
解构 state/getters 时保持响应性 |
| Actions 修改 State | 即使 Pinia 允许直接修改,仍建议通过 action 统一变更,便于调试 |
| Getters 计算派生状态 | 避免在模板中编写复杂逻辑 |
| 合理持久化 | 仅持久化必要字段,避免存储敏感信息和大数据 |
5.2 性能注意事项
- Pinia 基于 Vue3 的 Proxy 响应式系统,在深度嵌套对象和大规模状态变更场景下,性能显著优于 Vuex(约 20-50%)
- 单个 Store 的 state 不宜过大,否则会影响依赖追踪效率
- 高频触发场景下,Pinia 的吞吐量比 Vuex 高约 40-50%
Pinia 凭借简洁的语法、优秀的 TypeScript 支持和灵活的设计,已成为 Vue 生态中状态管理的事实标准。从 Vuex 到 Pinia 的迁移不仅是技术升级,更是开发体验和工程效率的跃升。在实际项目中,结合模块化设计与数据持久化方案,Pinia 能够胜任从中小型应用到大型复杂系统的所有场景。