Vuex与Pinia区别

文章目录

  • 前言
  • 一、架构对比
    • [1.1 数据流](#1.1 数据流)
    • [1.2 核心差异一览](#1.2 核心差异一览)
  • [二、Vuex 基本用法回顾](#二、Vuex 基本用法回顾)
    • [2.1 创建 Store](#2.1 创建 Store)
    • [2.2 组件中使用](#2.2 组件中使用)
    • [2.3 模块化](#2.3 模块化)
  • [三、Pinia 对应写法](#三、Pinia 对应写法)
    • [3.1 同一功能的 Pinia 实现](#3.1 同一功能的 Pinia 实现)
    • [3.2 模块化对比](#3.2 模块化对比)
  • [四、API 对照表](#四、API 对照表)
  • [五、为什么 Pinia 取代 Vuex](#五、为什么 Pinia 取代 Vuex)
    • [5.1 去除 mutations 的冗余](#5.1 去除 mutations 的冗余)
    • [5.2 更好的 TypeScript 支持](#5.2 更好的 TypeScript 支持)
    • [5.3 多 Store 替代单 Store + 模块](#5.3 多 Store 替代单 Store + 模块)
    • [5.4 原生 Composition API](#5.4 原生 Composition API)
  • 六、迁移策略
    • [6.1 何时迁移](#6.1 何时迁移)
    • [6.2 渐进式迁移步骤](#6.2 渐进式迁移步骤)
    • [6.3 迁移对照示例](#6.3 迁移对照示例)
  • 七、面试聚焦
    • [7.1 为什么 Pinia 取代 Vuex?](#7.1 为什么 Pinia 取代 Vuex?)
    • [7.2 Vuex 的 mutations 为什么必须同步?](#7.2 Vuex 的 mutations 为什么必须同步?)
    • [7.3 dispatch 和直接调用 action 的区别?](#7.3 dispatch 和直接调用 action 的区别?)
  • 八、易混淆点
  • 九、思考与练习
  • 总结

前言

Vuex 是 Vue 2 时代的官方状态管理方案,Pinia 是其继任者,也是 Vue 3 的推荐选择。本篇会讲清楚:

  • Vuex 与 Pinia 的架构差异
  • API 与模块化设计对比
  • 为什么 Pinia 取代 Vuex
  • 从 Vuex 迁移到 Pinia 的策略

一、架构对比

1.1 数据流

Vuex:严格单向,修改 state 必须经过 mutations

复制代码
组件 dispatch → actions(可异步)
                ↓
            mutations(必须同步)
                ↓
              state
                ↓
            getters → 组件

Pinia:去掉 mutations,actions 直接修改 state

复制代码
组件调用 → actions(同步/异步均可)
              ↓
            state
              ↓
          getters → 组件

1.2 核心差异一览

对比项 Vuex Pinia
Vue 版本 Vue 2 / Vue 3 Vue 3(推荐)
修改 state 必须 commit mutation actions 中直接改
mutations 有,必须同步
模块化 单 Store + 嵌套 modules 多个独立 Store
命名空间 namespaced: true 天然隔离,无需配置
TypeScript 需复杂类型声明 天然支持,完整推导
DevTools 支持 支持,集成更直观
体积 较大 更轻量(约 1KB)
Composition API 支持但体验一般 原生 Setup Store

二、Vuex 基本用法回顾

2.1 创建 Store

javascript 复制代码
// store/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0,
    user: { name: '', token: '' }
  },

  getters: {
    doubleCount: (state) => state.count * 2
  },

  mutations: {
    INCREMENT(state) {
      state.count++  // mutations 必须同步
    },
    SET_USER(state, user) {
      state.user = user
    }
  },

  actions: {
    async login({ commit }, credentials) {
      const res = await api.login(credentials)
      commit('SET_USER', res.data)  // 通过 commit 调用 mutation
    },
    increment({ commit }) {
      commit('INCREMENT')
    }
  }
})

2.2 组件中使用

javascript 复制代码
import { useStore } from 'vuex'
import { computed } from 'vue'

const store = useStore()

// 读取
store.state.count
store.getters.doubleCount

// 修改
store.commit('INCREMENT')
store.dispatch('login', { username, password })

2.3 模块化

javascript 复制代码
// store/modules/user.js
export default {
  namespaced: true,
  state: () => ({ token: '' }),
  mutations: {
    SET_TOKEN(state, token) { state.token = token }
  },
  actions: {
    login({ commit }, form) {
      commit('SET_TOKEN', 'xxx')
    }
  }
}

// store/index.js
import user from './modules/user'
import cart from './modules/cart'

export default createStore({
  modules: { user, cart }
})

// 组件中
store.dispatch('user/login', form)
store.state.user.token

三、Pinia 对应写法

3.1 同一功能的 Pinia 实现

javascript 复制代码
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),

  getters: {
    doubleCount: (state) => state.count * 2
  },

  actions: {
    increment() {
      this.count++  // 直接修改,无需 mutation
    },
    async login(credentials) {
      const res = await api.login(credentials)
      this.user = res.data  // 异步 action 中直接改 state
    }
  }
})
javascript 复制代码
// 组件中
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const store = useCounterStore()
const { count, doubleCount } = storeToRefs(store)

store.increment()
await store.login(form)

3.2 模块化对比

javascript 复制代码
// Vuex:单 Store + 嵌套 modules
store.state.user.token
store.dispatch('user/login')

// Pinia:多个独立 Store,按文件拆分
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'

const userStore = useUserStore()
const cartStore = useCartStore()

Pinia 每个 Store 即一个模块,无需 namespaced、无需在根 Store 注册 modules。


四、API 对照表

操作 Vuex Pinia
读取 state store.state.xxx store.xxx
读取 getter store.getters.xxx store.xxx
同步改 state store.commit('MUTATION', payload) store.action() 或直接改
异步操作 store.dispatch('action', payload) store.action()
批量修改 多个 commit store.$patch({ ... })
监听变化 store.watch() store.$subscribe()
重置 state 无内置 store.$reset()
组合式用法 useStore() + mapXxx useXxxStore() + storeToRefs

五、为什么 Pinia 取代 Vuex

5.1 去除 mutations 的冗余

Vuex 的 mutations 存在意义是 DevTools 时间旅行调试时能记录每次 state 变更。但实际开发中:

  • 简单修改也要写 mutation + action 两层,样板代码多
  • 异步场景必须在 action 里 commit mutation,流程繁琐

Pinia 去掉 mutations,actions 直接改 state,DevTools 仍能追踪变更。

5.2 更好的 TypeScript 支持

typescript 复制代码
// Vuex:需手动定义 State、Getters、Actions 类型,较繁琐
// Pinia:defineStore 自动推导,几乎零配置
export const useUserStore = defineStore('user', {
  state: (): { name: string } => ({ name: '' }),
  actions: {
    setName(name: string) { this.name = name }  // this 有完整类型
  }
})

5.3 多 Store 替代单 Store + 模块

Vuex 所有状态在一棵 state 树下,大型项目 modules 嵌套深、命名空间冗长。Pinia 按业务域拆成独立 Store 文件,职责清晰、按需引入。

5.4 原生 Composition API

Pinia 的 Setup Store 与 ref / computed 完全一致,可复用 composable,Vuex 4 虽支持 Vue 3 但体验仍偏 Options API。


六、迁移策略

6.1 何时迁移

场景 建议
新项目 直接用 Pinia
Vue 2 老项目 继续 Vuex,除非升级 Vue 3
Vue 3 新项目用了 Vuex 建议逐步迁 Pinia
Vue 3 大型老项目 Vuex 渐进式迁移,按模块逐个替换

6.2 渐进式迁移步骤

第一步:安装 Pinia,与 Vuex 共存

javascript 复制代码
import { createPinia } from 'pinia'
import store from './store'  // 旧 Vuex

app.use(store)
app.use(createPinia())

第二步:新功能用 Pinia,旧模块保持 Vuex

新页面、新 Store 用 Pinia 编写,不影响现有 Vuex 模块。

第三步:按模块逐个迁移

javascript 复制代码
// Vuex module: store/modules/user.js
// ↓ 迁移为
// Pinia store: stores/user.js

// 对照关系:
// state        → state / ref
// getters      → getters / computed
// mutations    → 合并到 actions
// actions      → actions
// namespaced   → 删除,独立 defineStore

第四步:组件中替换引用

javascript 复制代码
// 迁移前
import { useStore } from 'vuex'
const store = useStore()
store.dispatch('user/login', form)
store.state.user.token

// 迁移后
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
await userStore.login(form)
userStore.token

第五步:移除 Vuex

所有模块迁移完成后,删除 Vuex 依赖和 store/index.js

6.3 迁移对照示例

javascript 复制代码
// ===== Vuex =====
// store/modules/cart.js
export default {
  namespaced: true,
  state: () => ({ items: [] }),
  mutations: {
    ADD_ITEM(state, item) { state.items.push(item) }
  },
  actions: {
    addItem({ commit }, item) {
      commit('ADD_ITEM', item)
    }
  },
  getters: {
    total: (state) => state.items.length
  }
}

// ===== Pinia =====
// stores/cart.js
export const useCartStore = defineStore('cart', {
  state: () => ({ items: [] }),
  getters: {
    total: (state) => state.items.length
  },
  actions: {
    addItem(item) {
      this.items.push(item)  // mutation 逻辑直接放 action
    }
  }
})

没有官方自动化迁移工具,需手动逐个模块重构。


七、面试聚焦

7.1 为什么 Pinia 取代 Vuex?

  • 去掉 mutations,API 更简洁
  • 多 Store 替代单 Store + 嵌套模块
  • 原生 TypeScript 和 Composition API 支持
  • 更轻量,DevTools 体验更好

7.2 Vuex 的 mutations 为什么必须同步?

DevTools 需要记录每次 state 变更的快照做时间旅行。若 mutation 异步,变更时机不可追踪,调试会混乱。Pinia 通过其他机制实现 DevTools 追踪,不再需要 mutations 层。

7.3 dispatch 和直接调用 action 的区别?

  • Vuexdispatch('action') 返回 Promise,统一入口,可触发订阅
  • Pinia :actions 就是普通方法,直接 store.action(),返回值由函数自身决定

八、易混淆点

  1. Pinia 不是 Vuex 的简单封装:架构重新设计,不是换皮。
  2. Vuex mutations 必须同步:异步逻辑放 actions,通过 commit 改 state。
  3. Pinia 无 namespaced :每个 Store 天然独立,用 useXxxStore() 按需引入。
  4. 两者可共存:迁移期间 Vuex 和 Pinia 可同时使用。
  5. Vuex 4 仍可用于 Vue 3:但官方推荐 Pinia,新项目不应再选 Vuex。

九、思考与练习

1. Vuex 和 Pinia 的数据流有什么区别?

解析:Vuex 是 state → mutations → actions,改 state 必须 commit mutation;Pinia 去掉 mutations,actions 直接修改 state。

2. 为什么 Vuex 需要 mutations?

解析:保证 state 变更可追踪,配合 DevTools 时间旅行调试;mutations 必须同步以确保快照准确。

3. Pinia 如何实现模块化?

解析:每个业务域一个独立 Store 文件(defineStore),无需根 Store 注册 modules,无需 namespaced。

4. 从 Vuex 迁移 Pinia 的核心步骤?

解析:安装 Pinia 共存 → 新功能用 Pinia → 按模块逐个迁移(mutations 合并到 actions)→ 组件替换引用 → 移除 Vuex。

5. 新项目该选 Vuex 还是 Pinia?

解析:选 Pinia。API 更简洁、TS 更好、官方推荐,Vuex 仅适合维护 Vue 2 老项目。


总结

  • Vuex:state → mutations(同步)→ actions,单 Store + 嵌套 modules
  • Pinia:state → getters → actions(无 mutations),多 Store 独立
  • 取代原因:去掉 mutations 冗余、TS/Composition API 原生支持、模块化更清晰
  • 迁移策略:渐进式共存,按模块逐个迁移,mutations 逻辑合并到 actions
  • 选型:新项目用 Pinia;Vue 2 老项目继续 Vuex