Vuex版本演进与核心特性对比(附:Vuex 4 与 Pinia 核心区别对比)

Vuex版本演进与核心特性对比


Vuex主要经历了两个关键版本:Vuex3(适配Vue2)和Vuex4(适配Vue3)。


核心区别包括:

  1. 创建方式:Vuex3使用new Vuex.Store(),Vuex4改用createStore()
  2. 访问方式:Vuex4新增useStore()组合式API访问
  3. 响应式系统:Vuex4基于Vue3的Proxy实现
  4. TypeScript支持:Vuex4提供原生TS支持
  5. 多例模式:Vuex4支持多个独立Store实例

数据流转原则保持不变(单向数据流),但Vuex4优化了类型推导和组合式API集成。


Mutations仍是唯一修改State的途径,Getters仅用于数据派生。


版本选择应匹配项目使用的Vue版本。


Pinia作为Vue3推荐的状态管理方案,移除了Mutations概念,允许Actions直接修改State,减少了代码层级,提供更好的TypeScript支持。


关联阅读推荐

Vuex 全局状态管理(附:常见错误)

Vuex4:专为 Vue 3 设计,提供完整 TypeScript 支持

Pinia Store 的两种书写(option 和 setup)方式对比(注意直接解构 store 会丢失响应式)


Vuex的几个大版本的主要区别


Vuex 随着 Vue.js 主要版本的演进,经历了几个关键的大版本更新,其中最核心的版本对应关系是:Vuex 3 匹配 Vue 2,Vuex 4 匹配 Vue 3


下面这个表格详细梳理了它们之间的主要区别:

特性维度 Vuex 3 (for Vue 2) Vuex 4 (for Vue 3)
核心兼容性 专为 Vue 2 设计,完全兼容 -1-4 专为 Vue 3 设计,是 Vue 3 的官方推荐版本 -2-5-9
创建 Store 方式 使用 new Vuex.Store({...}) 创建 -6 使用 createStore({...}) 函数创建 -5-6-10
在组件中访问 通过 this.$store 在选项式 API 中全局访问 -4-6 除了保留 this.$store 的访问方式,更推荐在组合式 API 中使用 useStore() 函数来获取 store 实例 -5-6
响应式系统 基于 Vue 2 的 Object.defineProperty 实现 -6 基于 Vue 3 的 Proxy 实现,性能更优,响应性更强 -6
多例模式支持 整个应用默认是单例模式,通常只有一个 Store -6 支持多例模式,可以通过 useStore 在不同的组件中创建或使用多个独立的 Store 实例 -6
TypeScript 支持 支持有限,需要额外配置才能获得较好的类型推断 -1-9 原生支持 TypeScript,提供了更好的类型推断和代码提示 -1-9
辅助函数 在选项式 API 中,可以无缝使用 mapStatemapGetters 等辅助函数 -6-8 辅助函数依然可用,但无法在组合式 API 的 setup 函数中使用。 *** ** * ** *** 官方推荐在 setup 中直接使用 useStore 并结合 computed 来实现相同的效果 -6
模块热替换 原生不支持模块热替换(HMR) -9 支持状态分割和模块热替换(HMR),在开发时更新模块无需重载页面 -9
安装命令 npm install vuex@3 -1-2 npm install vuex@nextnpm install vuex@4 -2-5-10

🌳 版本选择建议

总的来说,选择哪个版本完全取决于你项目所使用的 Vue 版本:

  • 如果你的项目基于 Vue 2 :请选择 Vuex 3 。这是最稳定、最兼容的选择,社区资源和文档也最丰富 -1-4

  • 如果你的项目基于 Vue 3 :请选择 Vuex 4 。它完全为 Vue 3 的组合式 API 设计,并带来了更好的 TypeScript 支持和性能优化 -2-5


数据流转方式的变化


关于数据流转方式,核心思想没有变化,依然是单向数据流 。但在实现细节TypeScript 的类型推导上,Vuex 4 (Vue 3 版本) 有了一些显著的改进。


下面我来对比一下数据流转在 Vuex 3Vuex 4 中的区别,以及背后相同的基本原则:


1. 核心流程未变:单向数据流

无论是 Vuex 3 还是 Vuex 4,它们都严格遵循 单向数据流 的架构模式。


这是 Flux 架构的精髓,在 Vuex 中体现为以下闭环:

  1. 组件(View) :通过 Dispatch 触发 Actions(处理异步)。

  2. Actions :通过 Commit 触发 Mutations。

  3. Mutations唯一 可以修改 State 的地方。

  4. State:变化后,驱动视图(View)重新渲染。


结论: 数据的流向 (View -> Action -> Mutation -> State -> View) 没有变化,这个核心设计模式在两个版本中是保持一致的。


2. 主要变化:API 调用方式与类型推导

虽然流程没变,但写法 (特别是结合组合式 API)和类型推导有了较大变化。

环节 Vuex 3 (Vue 2) Vuex 4 (Vue 3) 变化解读
触发 Action this.$store.dispatch('xxx', payload) 选项式 API: this.$store.dispatch('xxx', payload) 组合式 API: const { dispatch } = useStore() 然后调用 dispatch('xxx', payload) Vuex 4 在 Vue 3 的组合式 API 中,需要通过 useStore() 钩子获取 store 实例,而不是直接从 this 上取。
提交 Mutation this.$store.commit('xxx', payload) 选项式 API: this.$store.commit('xxx', payload) 组合式 API: const { commit } = useStore() 然后调用 commit('xxx', payload) 同上,主要是获取 store 实例的方式发生了变化,但 commit 方法的参数和用法完全一致。
读取 State this.$store.state.xxx 选项式 API: this.$store.state.xxx 组合式 API: const state = useStore().state 在组合式 API 中,直接从 store 对象上取 state 属性。不过,为了让 state 是响应式的,通常会用 computed 包裹: const count = computed(() => useStore().state.count)
TypeScript 类型推导较弱,需要手动声明模块扩展才能给 this.$store 加上类型。 类型推导大幅增强。useStore 函数可以结合 Injection Key 使用,使得在组件中访问 stategettersdispatch 时,能够自动推导出具体的类型。 这是 Vuex 4 在数据流转体验上的一个重大改进 ,现在调用 dispatch 时能明确知道有哪些 Action 名称以及 payload 应该是什么类型了。

3. 一个容易被忽略的变化:Store 的挂载方式

虽然这不直接改变数据在组件内部的流转方式,但它影响了 Vuex 如何"接入"应用的数据流系统:

  • Vuex 3 (Vue 2):

    javascript 复制代码
    new Vue({
      store, // 直接把 store 实例传入
      render: h => h(App)
    }).$mount('#app')
  • Vuex 4 (Vue 3):

    javascript 复制代码
    const app = createApp(App)
    app.use(store) // 通过 app.use 插件机制挂载
    app.mount('#app')

    Vue 3 使用了 app.use() 来安装插件,这使得 Vuex 在底层依赖注入机制上有所变化(从 Vue 2 的原型继承变成了 Vue 3 的依赖注入 provide/inject)。


总结

  • 数据流向没变: 依然是 Action (异步) -> Mutation (同步修改) -> State 的单向流。

  • 调用方式微调: 主要针对 Vue 3 的组合式 API,引入了 useStore() 来替代过去的 this.$store

  • 类型安全提升: Vuex 4 + TypeScript 的组合让整个数据流转过程变得更加可控和可维护。


如果你之前熟悉 Vuex 3,那么上手 Vuex 4 几乎没有什么心智负担,只需要注意在 setup 函数中改用 useStore 来获取实例即可。


Mutations:唯一 可以修改 State 的地方?

Vue页面使用计算属性,绑定state中的数据,同步更新视图?

还有getters也能修改state吗?


简单来说,结论是这样的:

  1. Mutations 是唯一可以修改 State 的地方:这句话依然成立。

  2. 计算属性绑定 state :这是读取和响应数据,不是修改,所以完全符合规则。

  3. Getters 不能修改 State:它的作用类似于计算属性,只做数据派生和返回,绝不改变源数据。


下面是详细的解释:


1. Mutations:确实是"唯一"的修改者

这里的"唯一"指的是对 State 进行写入操作的唯一途径。

你提到的"Vue页面使用计算属性绑定state中的数据,同步更新视图",这里的同步更新视图 其实是State 变化后,视图自动更新的结果,而不是计算属性修改了 State。

  • 流程还原

    1. 用户在页面上点击了一个按钮(触发事件)。

    2. commit 了一个 Mutation。

    3. Mutation 内部修改了 state.count

    4. 因为 state.count 变了,所以依赖于 state.count 的那个计算属性会自动重新计算

    5. 视图随之更新。

所以,是 Mutation 修改 State 导致了视图更新,而不是视图(或计算属性)直接修改了 State。 你的理解其实是对的,只是顺序需要理清:计算属性是 State 的消费者,不是修改者。


2. 计算属性绑定 State:这是正确的使用方式

计算属性在这里扮演的是一个响应式桥梁 的角色。在 Vue 组件中,你当然不应该直接修改 State(比如 this.$store.state.count = 1 是不允许的),但你应该通过计算属性来读取 State。

  • Vuex 3 (Vue 2):

    javascript 复制代码
    computed: {
      count() {
        return this.$store.state.count // 读取,没问题
      }
    }
  • Vuex 4 (Vue 3):

    javascript 复制代码
    import { computed } from 'vue'
    import { useStore } from 'vuex'
    
    setup() {
      const store = useStore()
      const count = computed(() => store.state.count) // 读取,没问题
      return { count }
    }

在这两个例子中,计算属性只是把 store 里的值映射 到了组件里。当 store.state.count 变化时,计算属性会跟着变,视图也跟着变。这完全符合单向数据流。


3. Getters:绝不能修改 State

Getters 的定位非常明确:它是 Store 的"计算属性"

  • Getters 的作用 :它接收 state 作为参数,对 state 进行一些派生(比如过滤列表、统计数据),然后返回一个结果。

  • Getters 不能做的事 :在 Getter 内部去修改 state(例如 state.count = 2)是不合规范的,而且即使你写了这样的代码,Vuex 也不会通过 Getter 来追踪 State 的变化。

  • 官方设计意图 :Getters 应该是纯函数------同样的输入(state)始终返回同样的输出,且没有副作用(不修改外部变量)。如果 Getter 能修改 State,那数据流就乱套了,调试也会变得非常困难。


总结:三者的分工

为了让你更清晰地理解,我把这三者的分工用表格总结一下:

角色 权限 主要功能 能否修改 State
State 数据源 存储应用的所有状态 (N/A - 它是被修改的对象)
Mutations 唯一写入者 同步地修改 State (唯一能修改的地方)
Getters 数据派生 从 State 中计算出衍生数据(类似于过滤、计数) 不能
组件计算属性 数据消费者 将 Store 中的 State 或 Getters 映射到组件中,用于视图渲染 不能

以下是 Vuex 4 和 Pinia 的详细对比表格,基于上一个问题(关于 mutations 的唯一修改权),在表格中能看到 Pinia 最大的变化正是移除了 mutations。


Vuex 4 与 Pinia 核心区别对比

特性维度 Vuex 4 (for Vue 3) Pinia (官方推荐)
设计哲学 遵循经典的 Flux 架构,强调严格的单向数据流 和通过 mutations 显式修改状态的规范性 -1-7 设计更简洁,旨在充分利用 Vue 3 的组合式 API,对开发者更友好,同时保持了状态管理的核心能力 -3-7
API 结构与核心概念 包含四个核心概念stategettersmutations (用于同步修改)、actions (用于异步和提交 mutations)。修改状态的唯一途径是提交 mutation -10 只有三个核心概念stategettersactions完全移除了 mutationsactions 可以直接修改 state,无论是同步还是异步操作 -1-3-9
状态修改方式 繁琐 :必须通过 commit('mutationName', payload) 触发 mutation 来修改 state -6-9 直观灵活 :可以直接修改 state(如 store.count++),更推荐将逻辑封装在 actions 里,直接对 this 上的状态进行赋值(如 this.count++-1-6-9
模块化 通过 modules 属性进行嵌套模块划分。当模块相互引用或结构复杂时,配置可能变得繁琐,通常需要手动开启 namespaced: true 来避免命名冲突 -6-8-9 天然支持模块化 。每个 useStore 返回的实例都是一个独立的模块,通过导入不同的 store 函数来使用。结构扁平,无需处理命名空间,在任意 store 之间可以相互调用 -1-3-7
TypeScript 支持 支持有限 。需要进行繁琐的手动类型声明才能获得较好的类型推断,开发体验和代码健壮性相对较弱 -1-5-7 原生完美支持 。API 的设计使其能自动推断所有类型,无需或仅需极少的手动类型声明,提供了极佳的 TypeScript 开发体验 -1-6-9
代码冗余度 (样板代码) 较高 。需要为同步(mutations)和异步(actions)编写两套代码,即使操作逻辑完全一样 -1-6 极低 。去除了 mutations,所有逻辑统一在 actions 中,显著减少了样板代码,使代码更加简洁清晰 -1-9
响应式原理 依赖于 Vue 3 的 reactive() 实现响应式 -5 同样基于 Vue 3 的 reactiveref,响应式处理更自然,与组合式 API 完美融合 -7-8
在组件中使用 (Composition API) 使用 useStore() 函数获取 store 实例,然后通过 store.statestore.getters 访问。 直接导入对应的 useXXXStore 函数,调用后返回 store 实例,直接通过实例属性访问 stategetters,通过方法调用 actions -3-9

总结与选型建议

  • 选择 Vuex 4 的场景

    • 你的项目已经基于 Vuex 4 构建,迁移成本较高。

    • 团队习惯了严格的 mutation 提交规范,认为这有助于在大型团队中维持代码的可预测性 -1

  • 选择 Pinia 的场景 (更推荐)

    • 启动全新的 Vue 3 项目 :Pinia 已被官方认定为 Vue 3 的推荐状态管理方案 -7-9

    • 追求开发效率和代码简洁 :如果你希望减少样板代码,让状态管理逻辑更清晰、更现代,Pinia 是更好的选择 -1-3

    • 重度使用 TypeScript :Pinia 无需额外声明即可获得完美的类型推断,能极大提升开发体验和应用稳定性 -1-6-9


Pinia 移除了 mutations,这直接回应了之前关于"唯一修改源"的讨论。现在修改状态更直接了,但数据的流转和响应式更新依然清晰可追溯。


Pinia 的数据流转


Pinia 的数据流转相比于 Vuex 4 更加简洁直接 。由于它彻底移除了 mutations ,整个数据流动的路径变短了,但单向数据流的核心思想依然保留


以下是 Pinia 的数据流转示意图和详细说明:

1. Pinia 数据流转流程图

javascript 复制代码
Vue 组件 (Component)
          │
          │ 1. 调用 Action
          │    (store.increment())
          ▼
    ┌─────────────────┐
    │   Pinia Store   │
    │  (Actions)      │
    └─────────────────┘
          │
          │ 2. 直接修改 State
          │    (this.count++)
          ▼
    ┌─────────────────┐
    │   State (数据源) │
    └─────────────────┘
          │
          │ 3. 状态变化触发响应式更新
          │    (基于 Proxy)
          ▼
    Vue 组件重新渲染 (View)

2. 数据流转步骤详解

第一步:组件调用 Action

在组件中,你通过调用 Store 暴露出的 Action 方法来触发状态变化。

javascript 复制代码
// Vue 组件 (Counter.vue)
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()

// 用户点击按钮后,直接调用 Action
const handleClick = () => {
  counterStore.increment() // 调用 Action,而不是 commit
}
第二步:Action 直接修改 State

在 Action 函数内部,你可以直接修改 State。这是 Pinia 和 Vuex 最大的区别------不再需要通过 Mutation,Action 直接操作 State

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

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Pinia'
  }),
  actions: {
    // 同步 Action
    increment() {
      // 直接修改 state,无需 commit
      this.count++
    },
    // 异步 Action
    async fetchData() {
      const data = await api.getData()
      // 异步获取数据后,直接修改 state
      this.name = data.name
    },
    // 混合操作
    complexUpdate() {
      // 可以调用其他 action
      this.increment()
      // 直接修改
      this.name = 'Updated'
    }
  }
})
第三步:State 变化触发视图更新

State 的变化通过 Vue 3 的响应式系统(基于 Proxy)自动检测到,并触发所有依赖该状态的组件重新渲染。

javascript 复制代码
// Vue 组件 (Counter.vue)
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()

// 模板中直接使用 state
// 当 counterStore.count 变化时,视图自动更新
// <div>{{ counterStore.count }}</div>

3. Pinia 数据流转的三种方式

Pinia 给了你更多灵活性,你可以根据场景选择合适的数据流转方式:

操作方式 示例代码 适用场景
直接修改 State counterStore.count++ 简单的、临时的、组件内的状态修改(不推荐在大型应用中使用,不利于调试)
通过 Action 修改 (同步) counterStore.increment() 封装了业务逻辑的同步操作(推荐)
通过 Action 修改 (异步) await counterStore.fetchData() 包含异步 API 调用、复杂逻辑的状态修改(推荐)

4. Pinia 中的 Getters (派生数据)

在数据流转中,Getters 的角色没有变化,依然是从 State 中派生数据

javascript 复制代码
// stores/counter.js
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    // 自动缓存,依赖的 state 变化时重新计算
    doubleCount: (state) => state.count * 2,
    // 使用 this 访问其他 getter
    doubleCountPlusOne(): number {
      return this.doubleCount + 1
    }
  }
})

5. 与 Vuex 4 数据流转对比

为了更直观地理解 Pinia 的简洁性,这里对比一下两者的流程:

步骤 Vuex 4 数据流转 Pinia 数据流转
1 组件 dispatch 一个 Action 组件调用 Action 方法
2 Action 执行异步操作后 commit 一个 Mutation (无此步骤)
3 Mutation 修改 State Action 直接修改 State
4 State 变化 -> 视图更新 State 变化 -> 视图更新

Pinia 的优势体现

  • 减少了代码层级:少了一层 Mutations,逻辑更加直观。

  • 减少了样板代码:不再需要为每个状态变化写两个函数(一个 action,一个 mutation)。

  • 更好的 TypeScript 支持 :因为直接修改 this,TypeScript 可以完美推断类型,无需手动声明。


总结:Pinia 的数据流转特点

  1. 路径更短组件 -> Action -> State -> 视图

  2. 灵活性更高:Action 既可以处理同步,也可以处理异步,都可以直接修改 State。

  3. 开发体验更好:少了一层概念,代码写起来更自然,就像在修改一个普通的 JavaScript 对象。

  4. 调试依然可靠:虽然移除了 Mutations,但配合 Pinia 的 Devtools 插件,依然可以清晰地追踪每次状态修改的历史记录。


如果你是从 Vuex 迁移过来,刚开始可能会不习惯直接在 Action 里修改 State,但很快你就会发现这种方式让代码更简洁、更容易维护。

相关推荐
Irene199120 小时前
对比总结:Vue3中的 watch 和 Pinia中的 $subscribe
vue.js·pinia·watch·subscribe
这是个栗子2 天前
【Vue3项目】电商前台项目(四)
前端·vue.js·pinia·表单校验·面包屑导航
还是大剑师兰特4 天前
Pinia在Vue3中的应用部署与使用,包括持久化方案
pinia·大剑师
p5l2m9n4o6q7 天前
Vue3后台管理系统布局实战:从零搭建Element Plus左右布局(含Pinia状态管理)
vue3·pinia·element plus·viewui·后台管理系统
天若有情6731 个月前
Vuex 的核心作用深度解析:构建高效可维护的 Vue 应用状态管理体系
前端·javascript·vue.js·vuex
小书包酱1 个月前
在 VS Code中,vue2-vuex 使用终于有体验感增强的插件了。
vue.js·vuex
Irene19912 个月前
在计算属性中获取 Vuex 状态是标准做法(附:Vue 3 计算属性详解及和 watch 对比)
vue.js·vuex·watch·计算属性
Irene19912 个月前
全局状态管理:Vuex 与 Pinia 对比(附:反模式详解)
pinia·vuex·状态管理·反模式
二哈喇子!2 个月前
Pinia 状态管理库
pinia