💥先说结论:除非你团队真的"特别大",否则别再迷信 Pinia
用完 Pinia,最后我自己掏出一套组合式 API 封装后,项目变清爽了、调试变简单了、开发变自由了。
Pinia 确实"像 Vuex 的现代替代品",但它仍然是一种「状态管理框架」,一旦项目复杂,它就成了"另一个 Vuex"。
组合式 API +
ref()
+ 封装逻辑函数,其实就是你早就拥有的"更强 store"。
🧱背景:Pinia 被推荐是因为「轻量 + DevTools 支持 + 自动类型推导」
这是我最早喜欢 Pinia 的理由。你看这写法:
ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
info: null,
}),
actions: {
setToken(token: string) {
this.token = token
},
},
})
是不是熟悉的感觉?既有 state
,又有 actions
,还能在组件里这样用:
ts
const userStore = useUserStore()
userStore.setToken('abc')
但你写着写着,问题就来了。
😫痛点 1:复杂逻辑封装极难
来个真实案例: 登录成功后,要做三件事:
- 存 token
- 拉用户信息(异步)
- 如果用户是"新用户",还要弹一个 modal
你是不是要这样写?
ts
actions: {
async login(payload) {
const res = await loginApi(payload)
this.token = res.token
const userInfo = await fetchUserInfo()
this.info = userInfo
if (userInfo.isNew) {
// 在 store 里开弹窗?
// 还是 emit 事件?
// 还是从组件层判断?
}
}
}
你会发现:store 被耦合了 UI 行为。
你只想"登录",结果 store
里塞了一堆 UI 流程代码。 最后它变成了另一个"小 app.vue"。
😤痛点 2:模块拆分没有比组合式 API 更清晰
Pinia 多模块管理要这样:
ts
// user.ts
export const useUserStore = defineStore(...)
// cart.ts
export const useCartStore = defineStore(...)
// 然后全局注册、统一引入
看似干净,但本质还是 Vuex 那套模块思路,你想拆模块,要考虑:
- 类型合并
- 名字冲突
- state 嵌套结构暴露还是封装?
但你换成组合式 API 呢?
ts
// user.ts
export const useUser = () => {
const token = ref('')
const info = ref(null)
const setToken = (t: string) => token.value = t
return {
token, info, setToken
}
}
是不是就和普通 setup()
一样?用的时候直接:
ts
const user = useUser()
user.setToken('abc')
逻辑拆分天然用文件系统组织,而不是人为"store 注册"绑定。
🐛痛点 3:调试复杂,响应性还是靠 guess
你以为写了:
ts
state: () => ({
list: [],
count: 0
})
就响应式了?其实不是。
当你写:
ts
this.list.push(123)
你有没有遇到页面没更新 的情况?因为不是用 this.list = [...this.list, 123]
。
这时你得问自己:为什么我要去想"哪种操作才是响应式"?
组合式 API 根本不用想,直接:
ts
const list = ref<number[]>([])
list.value.push(123) // 100% 响应式
还支持解构、传递、深度操作,不用 this
,不用猜。
⚙️实战:组合式 API 怎么封装出"类 Store"模块?
你可以像这样封一个 user.ts
模块:
ts
// user.ts
import { ref } from 'vue'
const token = ref('')
const info = ref(null)
const setToken = (t: string) => token.value = t
const setInfo = (i: any) => info.value = i
export function useUser() {
return {
token,
info,
setToken,
setInfo,
}
}
用的时候:
ts
import { useUser } from '@/composables/user'
const user = useUser()
user.setToken('abc')
要是你怕每次都用一个新实例,也可以加缓存:
ts
let instance: ReturnType<typeof createUser> | null = null
function createUser() {
const token = ref('')
...
return { token, ... }
}
export const useUser = () => {
if (!instance) instance = createUser()
return instance
}
这不就是你熟悉的 useXxxStore()
?
✅什么时候用 Pinia 更合适?
Pinia 并不是没价值,它在以下场景更适用:
- 大型团队需要明确 store 组织结构
- 需要 SSR 支持的场景(如 Nuxt 3 有原生支持)
- 习惯"集中式状态管理"的开发者(类 Redux / Vuex)
如果你有几十个状态模块、多人维护、需要强类型约束和统一 DevTools,那 Pinia 是更稳的方案。
❗但如果你是中小团队、独立开发、组件灵活变更多......
用组合式 API 代替 Pinia,是一个完全自然的进化。
你会得到:更简单的状态定义, 更自由的逻辑组合, 更少的心智负担, 更轻的项目依赖(减少打包体积)
Pinia 有它的价值的,但不是每个 Vue 项目都需要它。
Vue 3 最大的价值,就是组合式 API 足够强,已经能当 Store 了。
所以在未来,我倾向这样的架构:
- 轻状态 :组合式 +
ref()
封装逻辑模块 - 重状态(例如缓存 / SSR / 跨页):再考虑 Pinia or Vuex
你们怎么看🙂