讲 Vuex 之前,先说一下痛点。在复杂的 Vue 项目中,组件间的通信通常会演变成“面条代码”(错综复杂、逻辑混乱):
Vuex 的本质解决方案: 将分散在各处的“私有状态”抽离出来,建立一个全局单例模式的“中央仓库”
Vuex 并不是一个简单的全局变量,它强制执行一套**“可预测”**的变更规则。
Vuex 3 的核心是利用一个隐藏的 Vue 实例
内部实例化了一个隐藏的 Vue 实例。将 state 存放在该实例的 data 中,利用 Vue 2 的 Object.defineProperty 实现
// 源码简化逻辑
store._vm = new Vue({
data: {
$$state: state // 借用 Vue 的 data 实现响应式
},
computed: wrapperGetters // 把 getters 变成这个实例的计算属性
}) 原理: 当你执行 Vue.use(Vuex) 时,Vuex 插件会在全局混入一个 beforeCreate 钩子。
分发逻辑:
1. 根组件(Root)在初始化时接收了 new Vuex.Store 实例。
2. 混入的钩子会检查当前组件:如果你有 store 选项,我就把它存到 this.$store。
3. 如果你没有(比如子组件),我就去this.$parent(父组件)那里拿它的 $store。
结果: 就像接水管一样,Store 沿着组件树一层层向下传递,最终所有组件的 this.$store 都指向了同一个内存地址
Vuex 4 抛弃了 Vue 实例,直接使用 Reactivity API。
import { reactive, computed } from 'vue'
this._state = reactive({ data: state })原理: 当你执行 app.use(store) 时,Vuex 4 内部调用了 app.provide('store', store)。
分发逻辑: Vue 3 引擎会在底层维护一个 provides 对象池。任何子组件通过 inject('store') 都能直接从这个池子里拿到引用。
作用: 这种方式更符合解耦原则。它不再强依赖于 $parent 这种父子链条,而是通过上下文(Context)直接获取,性能更高,也更安全。
几乎每个页面、每个组件都需要知道当前登录的是谁,以及他有没有权限操作某个按钮。
影响整个应用表现的控制开关,通常由用户在“设置”中触发。
当两个组件在 DOM 树上相隔极远(比如 A 是 B 的曾孙组件的邻居),但它们又必须频繁同步状态时。
“Vuex 4 虽然适配了 Vue 3,但它依然背负着旧时代的‘包袱’:强制的 Mutation、繁琐的类型推导、以及那个无法拆分的单例大对象
在 Vuex 中,当你执行 new Vuex.Store({ modules: { ... } }) 时,底层发生了以下几件事:
Pinia 彻底推翻了“单一实例”的物理结构,改成了**“工厂模式”**。
| 特性 | Vuex 4 | Pinia |
|---|---|---|
| 修改状态 | commit('mutationName', payload) | 直接赋值 store.state.count = 1 |
| 异步操作 | dispatch('actionName') | 直接在 action 中写异步代码 |
| 访问状态 | computed(() => store.state.cart.items) | store.cart.items (自动响应式) |
| 模块化 | modules 嵌套,需要 namespaced | 每个 store 独立,扁平结构 |
| 跨模块访问 | 通过 rootState | 直接 import 其他 store |
| TypeScript | 需要额外类型声明 | 原生完美支持 |
| 代码量 | 较多(mutations + actions) | 很少(只有 state + actions) |
静态注册 VS 动态注册的架构差异:
| 特性 | Vuex 4 | Pinia |
|---|---|---|
| 注册时机 | 应用启动时递归合并所有模块 | 组件实际使用时才创建 |
| main.js 需要做的 | app.use(store) + 导入所有模块 | 仅 app.use(pinia) |
| 初始化开销 | 启动时完整初始化所有模块 | 按需初始化,启动极快 |
| store 实例 | 单一全局实例 | 每个 store 独立实例(但单例) |
| 生命周期 | 应用级全局单例 | 应用级单例,但懒加载 |
