Vue 项目不要再用 Pinia 了,组合式 API + ref() 才是王道

💥先说结论:除非你团队真的"特别大",否则别再迷信 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:复杂逻辑封装极难

来个真实案例: 登录成功后,要做三件事:

  1. 存 token
  2. 拉用户信息(异步)
  3. 如果用户是"新用户",还要弹一个 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 并不是没价值,它在以下场景更适用:

  1. 大型团队需要明确 store 组织结构
  2. 需要 SSR 支持的场景(如 Nuxt 3 有原生支持)
  3. 习惯"集中式状态管理"的开发者(类 Redux / Vuex)

如果你有几十个状态模块、多人维护、需要强类型约束和统一 DevTools,那 Pinia 是更稳的方案。


❗但如果你是中小团队、独立开发、组件灵活变更多......

用组合式 API 代替 Pinia,是一个完全自然的进化。

你会得到:更简单的状态定义, 更自由的逻辑组合, 更少的心智负担, 更轻的项目依赖(减少打包体积)

Pinia 有它的价值的,但不是每个 Vue 项目都需要它。

Vue 3 最大的价值,就是组合式 API 足够强,已经能当 Store 了。

所以在未来,我倾向这样的架构:

  • 轻状态 :组合式 + ref() 封装逻辑模块
  • 重状态(例如缓存 / SSR / 跨页):再考虑 Pinia or Vuex

你们怎么看🙂

📌 你可以继续看我的系列文章

相关推荐
墨夏21 分钟前
TS 高级类型
前端·typescript
程序猿师兄30 分钟前
若依框架前端调用后台服务报跨域错误
前端
前端小巷子33 分钟前
跨标签页通信(三):Web Storage
前端·面试·浏览器
工呈士34 分钟前
TCP 三次握手与四次挥手详解
前端·后端·面试
BillKu35 分钟前
Vue3 + TypeScript + Element Plus + el-input 输入框列表按回车聚焦到下一行
前端·javascript·typescript
复苏季风35 分钟前
前端程序员unity学习笔记01: 从c#开始的入门,using命名空间,MonoBehaviour,static,public
前端
阿古达木38 分钟前
沉浸式改 bug,步步深入
前端·javascript·github
夏沫mds38 分钟前
Hyperledger Fabric食品溯源
运维·vue.js·go·vue·区块链·gin·fabric
小泡芙丫39 分钟前
JavaScript 的 Promise:一场关于相亲、结婚与生子的异步人生大戏
javascript
stoneSkySpace1 小时前
react 自定义状态管理库
前端·react.js·前端框架