若川博客 Vuex 进阶:学习 vuex 源码整体架构,打造属于自己的状态管理库
2026/8/23 13:56:24 网站建设 项目流程

若川博客 Vuex 进阶:学习 vuex 源码整体架构,打造属于自己的状态管理库

【免费下载链接】blog若川的博客—撰写了学习源码整体架构系列几十篇。组织了源码共读活动,每周一起学习200行左右的源码,加我微信 ruochuan02 参与。项目地址: https://gitcode.com/gh_mirrors/blog51/blog

本文是「学习源码整体架构」系列的进阶篇,带你用断点调试 + 对象关系图的方式,完整看懂Vuex 源码整体架构:从Vue.use(Vuex)插件安装、new Vuex.Store构造函数,到state响应式原理与commitdispatch等核心 API 的实现。掌握这套思路,你不仅能秒懂 Vuex 原理、应对面试,还能亲手打造属于自己的状态管理库。

想动手调试的读者,可以先克隆本仓库,完整源码解析都放在了docs/vuex/目录下(仓库地址:https://gitcode.com/gh_mirrors/blog51/blog),配合 vuex-store-instance.xmind 思维导图一起看,事半功倍。

一、为什么 Vuex 源码值得读:千行代码的状态管理库

Vuex 源码打包后只有一千多行,规模小、结构清晰,是学习状态管理原理的最佳入口 🚀。它的核心设计只有三点:

  1. $store注入:通过全局Vue.mixin,在每个 Vue 实例的beforeCreate生命周期中注入同一个Store实例,所以组件里才能用this.$store.dispatch
  2. state响应式:本质是用new Vue()包了一层私有实例,state挂在其_data.$$state上;
  3. getters即计算属性:把用户定义的 getters 注册为 computed,白嫖了 Vue 计算属性的懒加载缓存。
// 响应式的核心就这一句: store._vm = new Vue({ data: { $$state: state }, // 用户定义的 state computed // 处理后的 getters })

也就是说,你在模板里写的$store.state.count,最终源码路径是:

vm.$store._vm._data.$$state.count

二、Vue.use(Vuex) 插件安装:Vuex 对象结构一图看懂

先看Vuex对象的全貌,它本质是Vue 的一个插件,包含installStoremapState等 4 个辅助函数:

安装流程非常简单:

  1. Vue.use(Vuex)触发install(_Vue)函数(vuex/src/store.js):记录Vue引用,然后调用applyMixin
  2. applyMixinvuex/src/mixin.js)执行Vue.mixin({ beforeCreate: vuexInit })
  3. vuexInit把根实例的store逐级向下注入,最终每个 Vue 实例都持有同一个Store实例:
function vuexInit () { const options = this.$options if (options.store) { this.$store = options.store } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store } }

💡 用购物车例子验证:vm.$store === vm.$children[0].$store结果为true,所有组件共享同一个 store。

三、new Vuex.Store 构造函数:记住三行核心代码

new Vuex.Store(options)之后,Store 实例长这样(这是全文最重要的一张图):

构造函数的执行流程可以概括为「三段式」:

第一段:条件断言。不满足直接抛错,这是 Vuex 报错信息的出处:

assert(Vue, 'must call Vue.use(Vuex) before creating a store instance.') assert(typeof Promise !== 'undefined', 'vuex requires a Promise polyfill...') assert(this instanceof Store, 'store must be called with the new operator.')

第二段:声明内部变量this._actionsthis._mutationsthis._wrappedGetters用于存放处理后的用户代码;this._modules = new ModuleCollection(options)构造模块树。这里用了Object.create(null)(无原型链的空对象)而非{},是个值得注意的细节。

第三段:三行核心代码,初读构造函数只需要盯住它们:

this._modules = new ModuleCollection(options) // 1. 收集模块,构造模块树 installModule(this, state, [], this._modules.root) // 2. 注册 state/mutation/action/getter resetStoreVM(this, state) // 3. new Vue 实现 state 响应式

之后还有两个收尾动作:plugins.forEach(plugin => plugin(this))执行所有插件,以及按需初始化 vue-devtools 调试工具。

四、ModuleCollection:把模块变成一棵树

ModuleCollectionvuex/src/module-collection.js)负责把用户new Vuex.Store时传入的 options 加工成模块树,构造函数就一行:this.register([], rawRootModule, false),内部通过递归注册所有子模块。

每个节点都是一个Module实例(vuex/src/module.js),保存三样东西:

  • state:模块的状态(支持函数返回值或对象);
  • _children:子模块;
  • _rawModule:用户定义的原始模块。

以官方购物车示例(vuex/examples/shopping-cart)为例,store._modules最终长这样:

installModule函数则遍历这棵树,完成四件注册工作:

步骤作用
注册 state非根模块通过Vue.set挂到父级 state 上,保证响应式
注册 mutation包一层wrappedMutationHandler,这就是 mutation 第一个参数是state的原因
注册 action包一层wrappedActionHandler,注入{ dispatch, commit, state, rootState }上下文,并自动 Promise 化
注册 getter包一层wrappedGetter,收集到_wrappedGetters

五、resetStoreVM:Vuex state 响应式原理全解

这是 Vuex 最巧妙的地方——用一个隐藏的 Vue 实例承载响应式

store._vm = new Vue({ data: { $$state: state }, // state 树变成响应式 computed // getters 变成计算属性 })

细节有三个:

  • getters 绑定:把_wrappedGetters中每一项包成computed(借助partial函数避免闭包持有旧 vm),再通过Object.defineProperty暴露到store.getters上,懒加载 + 缓存白嫖 Vue 计算属性机制;
  • 静默模式new Vue前临时打开Vue.config.silent,避免这个隐藏实例触发全局 mixin 的警告;
  • 严格模式:若strict: true,调用enableStrictMode$watch深度观测 state——没走_withCommit的修改都会报警,这就是 Vuex 强制「只能通过 mutation 改 state」的实现原理。

六、Vuex 核心 API:commit 与 dispatch 的实现

Store 原型上还有commitdispatchreplaceStatewatchsubscriberegisterModule等 API,核心两个的实现思路如下:

commit(提交 mutation):

commit (_type, _payload, _options) { const { type, payload } = unifyObjectStyle(_type, _payload, _options) const entry = this._mutations[type] this._withCommit(() => { // 标记「正在合法修改」 entry.forEach(handler => handler(payload)) }) this._subscribers.forEach(sub => sub(mutation, this.state)) // 触发订阅 }

dispatch(分发 action):取出_actions[type]中的处理器逐个执行,结果自动 Promise 化,并在执行前后触发before/after订阅者(subscribeAction注册的),这也是 Vuex 支持异步操作的根基。

另外store.watch是拿另一个隐藏实例_watcherVM.$watch实现的;registerModule/unregisterModule则演示了动态模块的完整生命周期。

七、mapState 等辅助函数:组件绑定的「语法糖」

mapStatemapGettersmapMutationsmapActions都位于vuex/src/helpers.js,实现套路高度统一:

  1. normalizeNamespace统一命名空间(没传就置空,末尾补/);
  2. normalizeMap把数组/对象统一成[{ key, val }]数组;
  3. 为每一项生成一个计算属性或方法,从this.$store(或命名空间模块的context)取值。

例如mapState在无命名空间时:

// 你写的 ...mapState({ checkoutStatus: state => state.cart.checkoutStatus }) // 等价于 checkoutStatus: function () { return this.$store.state.checkoutStatus }

createNamespacedHelpers('cart')则是把这四个函数bind上命名空间,方便命名空间模块集中使用。这些辅助函数配合第四节的module.contextmakeLocalContext生成),彻底抹平了「是否使用命名空间」的差异 ✨。

八、总结:动手调试,打造你自己的状态管理库

回顾一下 Vuex 源码整体架构的完整链路:

Vue.use(Vuex)安装插件 →new Vuex.Store断言 + 内部变量初始化 →ModuleCollection构造模块树 →installModule注册 state/mutation/action/getter →resetStoreVMnew Vue实现响应式 →commit/dispatch/watch等 API 围绕store._vm服务 →mapState等辅助函数抹平命名空间差异。

几个调试建议(来自作者的实战经验):

  • 先看官方 Shopping Cart 例子(vuex/examples/shopping-cart),把store实例在控制台打印出来对照关系图看;
  • 给 webpack 配置加上devtool: 'source-map',然后在 Chrome 断点调试installModuleresetStoreVM两个函数即可串起主线;
  • 会调试代码,看源码就简单了——不调试只看文章,很难消化吸收。

Vuex 源码虽然只有千行出头,但把「响应式、模块化、插件化、订阅者模式」这些状态管理库的通用设计全用到了。读懂它之后,再去看本仓库中 Vuex 4(Vue 3 下基于provide/injectdocs/vuex4/README.md)、Redux(docs/redux/README.md)等系列分析,你会发现它们不过是同一套架构的不同变体。动手照着本文的链路写一个玩具版状态管理库,你离「源码大神」又近了一步 💪。

【免费下载链接】blog若川的博客—撰写了学习源码整体架构系列几十篇。组织了源码共读活动,每周一起学习200行左右的源码,加我微信 ruochuan02 参与。项目地址: https://gitcode.com/gh_mirrors/blog51/blog

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询