再来聊聊,Vue3 项目中 Pinia 的替代方案

大家好,我是刘布斯。

公众号上周转载了一篇文章Vue 项目不要再用 Pinia 了,先不可否认,这文章有点标题党的意思。但这篇文章的主要观点是说,在中小项目里,用 Vue 3 自带的组合式 API(reactive / ref)来管状态,很多时候比硬上 Pinia 要香。

好家伙,评论区一下就热闹了,总结起来是:“Pinia 多好用,你肯定是没用明白 Pinia”

说实话,我确实有点意外。

我先摆明态度:Pinia 是个非常优秀的状态管理库。 尤雨溪团队亲自操刀,API 设计简洁,TS 支持完美,插件系统灵活,Devtools 体验丝滑。这一点,没人能否认。

但我的核心观点是:优秀,不代表“所有场景都必须上”。

我发现现在很多团队,尤其是从 Vue 2 刚迁到 Vue 3 不久的,存在一种很强的 “状态管理惯性”

什么意思?

在 Vue 2 + Options API 的时代,组件(Component)和状态(State)是“隔离”的。data 里的状态天生就是“内向”的,组件一销毁,状态就没了。跨组件通信、全局状态共享,你怎么办?你没得选,你必须上 Vuex。Vuex 就像一个“中央空调”,你不用它,别的房间(组件)就享受不到冷气(状态)。

所以,Vue 2 时代养成了我们的思维定式:做项目 = Vue 全家桶 = Vue + Vue Router + Vuex。

到了 Vue 3,Vuex 退位,Pinia 继任。于是大家理所当然地把公式换成了:做项目 = Vue 3 + Vue Router + Pinia。

启动一个新项目,npm create vue@latest,一路 yes 下来,Pinia 就装好了。然后就开始 defineStore

但大家好像都忽略了 Vue 3 最大的革命性变化——组合式 API (Composition API) 本身,就已经是一种强大的状态管理模式了。

defineStore 帮我们做了什么?

还是用那篇文章中的例子,做一个最简单的“用户状态管理”,两种方式有什么区别。

1. Pinia 的方式:你得先在 stores/user.ts 里:

import { defineStore } from'pinia'
import { ref, computed } from'vue'

exportconst useUserStore = defineStore('user'() => {
// State
const token = ref(null)
const userProfile = ref(null)

// Getters
const isLoggedIn = computed(() => !!token.value)

// Actions
function login(data{
    token.value = data.token
    userProfile.value = data.profile
    // ... 存 localstorage
  }

function logout({
    token.value = null
    userProfile.value = null
    // ... 清 localstorage
  }

return { token, userProfile, isLoggedIn, login, logout }
})

然后在组件里 useUserStore()

2. 组合式 API 的方式:你在 stores/user.ts (对,你也可以叫 stores 目录,这只是个文件夹):

import { ref, computed, reactive } from'vue'

// 以前我们用 reactive,这里用 ref/computed 模拟 Pinia 的结构
const token = ref(null)
const userProfile = ref(null)

const isLoggedIn = computed(() => !!token.value)

function login(data{
  token.value = data.token
  userProfile.value = data.profile
// ... 存 localstorage
}

function logout({
  token.value = null
  userProfile.value = null
// ... 清 localstorage
}

// 导出一个 hook
exportfunction useUser({
return { token, userProfile, isLoggedIn, login, logout }
}

然后在组件里 useUser()


好了,你对比下这两段代码。

你发现了什么?

在第二种方式里,我只是删掉了 defineStore('user', ...) 那层“壳”,然后把导出的 useUserStore 改成了 useUser (叫什么都行)。

其他的逻辑,一模一样! 都是在用 ref 和 computed

Pinia 的 defineStore 在这个场景里,本质上就是帮你做了一件事:创建了一个跨组件共享的、响应式的单例。

但在 Vue 3 里,import 一个在模块顶层(module scope)定义的 ref 或 reactive 对象,它天生就是单例!它天生就是跨组件共享的!

那你可能会反问了:“那 Pinia 岂不是多此一举?”

不,它当然不是多此一举。它提供了 defineStore 这个“壳”,是为了给你带来额外的好处,最核心的就是:

  1. Devtools 集成:这是 Pinia 最大的杀手锏。你可以在时间轴上看到 action 的调用、state 的变更。
  2. 插件系统:比如实现数据持久化,Pinia 有现成的插件,defineStore 的时候配置一下就行。
  3. SSR 支持:在服务端渲染时,Pinia 能帮你处理好状态的序列化和“注水”(hydration)。
  4. 更严格的“心智模型”:它强制你区分 stategettersactions,让团队协作更规范。

Pinia的优势,你的项目真的需要吗?

我们再回到上篇文章的核心观点:

在一个中小型项目、独立开发、或者团队成员对组合式 API 都很熟练的场景下,上面 Pinia 提供的 4 个好处,你真的都需要吗?

  • Devtools:说实话,在我十多年的生涯里,除了在 Redux/Vuex 刚出来那会儿,为了调试复杂的异步流和中间件,会去用时间旅行。在绝大多数业务场景里,console.log 和 Vue Devtools 里自带的组件状态检查,已经解决了 99% 的问题。为了那 1% 的“可能”,去引入一个库,划算吗?
  • 插件系统(如持久化):用组合式 API 怎么做持久化?太简单了。你封装的 useUser hook 里面,login 的时候加一行 localStorage.setItem,初始化 token 的时候加一行 localStorage.getItem。这不就是最原始、最可控的持久化吗?你需要为这么点功能,去学一个 Pinia 插件的 API 吗?
  • SSR:如果你的项目压根就不是 SSR,那这条对你无效。
  • 严格的心智模型:这是最大的“陷阱”。组合式 API 的核心思想就是“自由”。它允许你把 state 和 action 放在一起,按“功能”去组织代码,而不是按“类型”(state/getter/action)去组织。defineStore 某种程度上,是又把我们拉回了 Vuex 的那种“分门别类”的思维里。

所以,“你会得到更少的心智负担”指的就是这个。

你不需要去记 defineStore 的 API,不需要去想“我这个逻辑是算 getter 还是 action”,你就是在写 JS/TS,你就是在写 function这难道不是一种解放吗?

什么叫“没用明白 Pinia”?

在我看来,恰恰相反。

把 Pinia 当成新时代的 Vuex,不分场景、启动项目就先装上,这才是“没用明白 Vue 3”。

如果没有明白 Vue 3 组合式 API 到底给了你多大的“自由”和“能力”,就可能继续用 Vue 2 的“保姆式”思维在写 Vue 3。

我想了几个场景,大家参考下:

  1. 场景一:个人项目、小团队敏捷开发、内部工具

    • 我的选择:100% 使用组合式 API。
    • 理由: 速度快,灵活,零依赖。我可以按功能拆分 useCounter.tsuseUser.tsuseCart.ts... 它们就是一堆 TS 模块,需要共享就在顶层定义 reactive,不需要就只导出函数。打包体积更小,心智负担为零。
  2. 场景二:大型企业级应用、多团队协作、需要强规范

    • 我的选择:我会倾向于使用 Pinia。
    • 理由: 这种项目,“规范”大于“灵活”。defineStore 提供的统一范式,能让不同水平的开发者(尤其是新人)写出风格更一致的代码。而且在这种复杂项目里,Devtools 的时间旅行和状态快照,在排查深层 Bug 时,确实能派上用场。
  3. 场景三:需要 SSR 的项目

    • 我的选择:用 Pinia。
    • 理由: 别重复造轮子。Pinia 对 SSR 状态处理得很好,自己搞一套组合式 API 的 SSR 状态同步,费时费力,不值得。

所以你看,并不是在全盘否定 Pinia,而是在呼吁大家 “按需选择”

不要因为“大家都用”或者“官方推荐”就去用。工具是死的,人是活的。Vue 3 给了我们一把更轻、更快的匕首(组合式 API),你为什么非要抱着那把很牛、但也很重的开山刀(Pinia)不放,连切个水果都要用它呢?

下次在项目里 npm install pinia 之前,先停 5 秒钟,问问自己:我这次,真的需要它吗?还是说,几个 ref 和 reactive 就能搞定了?

想明白这个问题,可能比你多刷 10 篇 Pinia 的教程都有用。

最后

还是老规矩,给大家推荐我们的刷题网站(https://fe.ecool.fun/)和同名的小程序【前端面试题宝典】。

如果你也觉得网上传的那些“八股文”太老了,翻来覆去就那几句,解决不了根本问题,一定要来看看我们的刷题工具。

我们这儿不搞虚的。目前已经塞进去 1600 多道题了,20 来个不同的分类,其中就包括面试官爱问的场景题,还不定期更新,目标就是做全网最新最全的。

尤其是,我们前段时间上线的【大厂一手面经】模块,这个我强烈建议你们都去小程序上看看(官网暂时不打算增加该模块)。

这可都是刚从字节、阿里、腾讯“战场”上下来的兄弟们热乎乎的分享。面试官到底问了啥、怎么问的、整个流程和注意事项、考察重点在哪,都给你们扒得明明白白。

现在这行情,兄弟们,信息差就是竞争力。早点知道别人在考什么,比你闷头多背一百道过时的“八股文”要管用得多。

大家可以去小程序【前端面试题宝典】,首页就能直接领那个【大厂真实面经】。

有需要开会员、或者想咨询辅导的,直接扫上面这个码,找我们的小助手就行。

行了,今天就聊到这。