大家好,我是刘布斯。
公众号上周转载了一篇文章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 这个“壳”,是为了给你带来额外的好处,最核心的就是:
action 的调用、state 的变更。defineStore 的时候配置一下就行。state、getters、actions,让团队协作更规范。我们再回到上篇文章的核心观点:
在一个中小型项目、独立开发、或者团队成员对组合式 API 都很熟练的场景下,上面 Pinia 提供的 4 个好处,你真的都需要吗?
console.log 和 Vue Devtools 里自带的组件状态检查,已经解决了 99% 的问题。为了那 1% 的“可能”,去引入一个库,划算吗?useUser hook 里面,login 的时候加一行 localStorage.setItem,初始化 token 的时候加一行 localStorage.getItem。这不就是最原始、最可控的持久化吗?你需要为这么点功能,去学一个 Pinia 插件的 API 吗?state 和 action 放在一起,按“功能”去组织代码,而不是按“类型”(state/getter/action)去组织。defineStore 某种程度上,是又把我们拉回了 Vuex 的那种“分门别类”的思维里。所以,“你会得到更少的心智负担”指的就是这个。
你不需要去记 defineStore 的 API,不需要去想“我这个逻辑是算 getter 还是 action”,你就是在写 JS/TS,你就是在写 function。这难道不是一种解放吗?
在我看来,恰恰相反。
把 Pinia 当成新时代的 Vuex,不分场景、启动项目就先装上,这才是“没用明白 Vue 3”。
如果没有明白 Vue 3 组合式 API 到底给了你多大的“自由”和“能力”,就可能继续用 Vue 2 的“保姆式”思维在写 Vue 3。
我想了几个场景,大家参考下:
场景一:个人项目、小团队敏捷开发、内部工具
useCounter.ts, useUser.ts, useCart.ts... 它们就是一堆 TS 模块,需要共享就在顶层定义 reactive,不需要就只导出函数。打包体积更小,心智负担为零。场景二:大型企业级应用、多团队协作、需要强规范
defineStore 提供的统一范式,能让不同水平的开发者(尤其是新人)写出风格更一致的代码。而且在这种复杂项目里,Devtools 的时间旅行和状态快照,在排查深层 Bug 时,确实能派上用场。场景三:需要 SSR 的项目
所以你看,并不是在全盘否定 Pinia,而是在呼吁大家 “按需选择”。
不要因为“大家都用”或者“官方推荐”就去用。工具是死的,人是活的。Vue 3 给了我们一把更轻、更快的匕首(组合式 API),你为什么非要抱着那把很牛、但也很重的开山刀(Pinia)不放,连切个水果都要用它呢?
下次在项目里 npm install pinia 之前,先停 5 秒钟,问问自己:我这次,真的需要它吗?还是说,几个 ref 和 reactive 就能搞定了?
想明白这个问题,可能比你多刷 10 篇 Pinia 的教程都有用。
还是老规矩,给大家推荐我们的刷题网站(https://fe.ecool.fun/)和同名的小程序【前端面试题宝典】。
如果你也觉得网上传的那些“八股文”太老了,翻来覆去就那几句,解决不了根本问题,一定要来看看我们的刷题工具。
我们这儿不搞虚的。目前已经塞进去 1600 多道题了,20 来个不同的分类,其中就包括面试官爱问的场景题,还不定期更新,目标就是做全网最新最全的。
尤其是,我们前段时间上线的【大厂一手面经】模块,这个我强烈建议你们都去小程序上看看(官网暂时不打算增加该模块)。
这可都是刚从字节、阿里、腾讯“战场”上下来的兄弟们热乎乎的分享。面试官到底问了啥、怎么问的、整个流程和注意事项、考察重点在哪,都给你们扒得明明白白。
现在这行情,兄弟们,信息差就是竞争力。早点知道别人在考什么,比你闷头多背一百道过时的“八股文”要管用得多。
大家可以去小程序【前端面试题宝典】,首页就能直接领那个【大厂真实面经】。
有需要开会员、或者想咨询辅导的,直接扫上面这个码,找我们的小助手就行。
行了,今天就聊到这。