在 Pinia 成为 Vue 官方默认状态管理方案之前,Vuex 是 Vue 2 时代的中大型项目标配。理解 Vuex 的设计有助于看清 Pinia 为什么做了那些改进,也有助于维护老项目。这一节先梳理 Vuex 的核心概念,再与 Pinia 进行务实对比。
Vuex 的核心概念
Vuex 把全局状态管理拆成五个固定角色:
- State:单一状态树,存储全部共享数据。组件通过
this.$store.state.xxx或mapState读取。 - Getters:类比计算属性,对 State 做派生计算。通过
this.$store.getters.xxx访问。 - Mutations:唯一可以修改 State 的方法,且必须是同步函数。组件通过
commit('mutationName', payload)触发。这个同步约束是为了让 DevTools 能记录每一次状态变更的快照,方便调试。 - Actions:处理异步操作(如接口请求),内部通过
commit调用 Mutation 来间接修改 State。组件通过dispatch('actionName', payload)触发。 - Modules:当状态树庞大时,用 Module 将 Store 分割成多个模块,每个模块拥有自己的 State、Getters、Mutations、Actions,最后合并到根 Store 上。可以通过
namespaced: true避免命名冲突。
一个典型的 Vuex 模块长这样:
// store/modules/user.js
export default {
namespaced: true,
state: {
name: '',
token: ''
},
mutations: {
SET_NAME(state, name) {
state.name = name
}
},
actions: {
async login({ commit }, payload) {
const res = await api.login(payload)
commit('SET_NAME', res.name)
}
}
}
在组件中使用时需要靠字符串匹配 Mutation/Action 名,或者引入辅助函数 mapState、mapActions 等,代码中会难以避免地出现各种字符串常量或辅助函数调用。
Pinia 相比 Vuex 的核心改进
Pinia 被设计为 Vuex 的“精神续作”,它保留了状态管理的核心思想,但大幅简化了概念和代码。可以用一句话概括它的思路:你只管定义 Store,然后像操作普通对象一样使用状态和方法。
具体改进体现在以下几点:
1. 概念精简:三合一
Vuex 有 State、Mutations、Actions、Getters 四个概念;Pinia 只保留了 State、Getters、Actions。没有 Mutations,也没有必须同步的限制。修改 State 可以直接 state.xxx = newValue,也可以在 Action 中异步完成复杂逻辑。修改操作不再强制拆分为“提交”和“动作”两步。
2. 写法更直觉,类型更友好
Vuex 的 Module 主要通过配置对象定义,对 TypeScript 支持需要额外包装。Pinia 的 Store 就是一个导出的函数(或对象),内部用 ref 和 computed 定义 State 和 Getters,用普通函数定义 Actions。代码完全感受不到“框架层级”的约束:
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const name = ref('')
const token = ref('')
const isLoggedIn = computed(() => !!token.value)
async function login(payload) {
const res = await api.login(payload)
name.value = res.name
token.value = res.token
}
return { name, token, isLoggedIn, login }
})
除了这种组合式写法,Pinia 也支持选项式的写法,适合从 Vuex 迁移的老项目。
3. 模块化变为“扁平化的多 Store 管理”
Vuex 必须通过 Modules 嵌套来拆分,最终仍挂在同一棵状态树上,状态层级深时容易混乱。Pinia 彻底抛弃了单一状态树的限制,一个 Store 就是一个独立的状态单元,多个 Store 之间可以通过导入直接互相调用,不存在 Modules 那套嵌套和命名空间问题。模块化的本质被还原为 JavaScript 原生的模块化(一个文件一个 Store)。
4. 没有 mapState 这些辅助函数的必要
Vuex 需要在组件中用 mapState 来优雅地展开 State,或用 $store.state.xxx 访问。Pinia 的 Store 实例本身就是响应式的,在组件中直接解构或用 store.xxx 读取,配合 storeToRefs 即可解决响应性丢失,写法更干净:
const userStore = useUserStore()
const { name, isLoggedIn } = storeToRefs(userStore)
userStore.login()
5. DevTools 支持更直观
Pinia 同样支持 Vue DevTools 的时间旅行和状态编辑,且因为抛弃了 Mutations,时间线上的每一条记录都是“直接操作”,更易于理解。此外 Pinia 还内置了插件机制、热更新支持、自动补全提示等现代特性。
选型结论
- 新项目一律用 Pinia:它是 Vue 官方推荐的默认状态管理方案,API 最简单,TS 支持最好,也是未来演化的重点。
- 维护 Vue 2 + Vuex 的老项目:如果项目已经稳定运行,继续使用 Vuex 没问题。若有机会升级到 Vue 3,建议逐步迁移到 Pinia,官方提供了迁移指导。
- 小项目或不需要全局状态时:不要因为“大家都用 Pinia”就强行引入。优先用组件内状态和 Provide/Inject,只有当状态需要跨多个非直接父子组件共享时才引入 Pinia。
总的来说,Pinia 是 Vuex 的“去概念化”重构:把“状态管理”这件事回归到“定义数据 + 定义操作数据的方法”,然后交给框架保持响应式。它的简洁不是削弱功能,而是去掉不必要的概念负担,让开发者更快地写出能跑、能维护的代码。