公司动态

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

📅 2026/8/23 13:56:40
若川博客 Vuex 进阶:学习 vuex 源码整体架构,打造属于自己的状态管理库
若川博客 Vuex 进阶学习 vuex 源码整体架构打造属于自己的状态管理库【免费下载链接】blog若川的博客—撰写了学习源码整体架构系列几十篇。组织了源码共读活动每周一起学习200行左右的源码加我微信 ruochuan02 参与。项目地址: https://gitcode.com/gh_mirrors/blog51/blog本文是「学习源码整体架构」系列的进阶篇带你用断点调试 对象关系图的方式完整看懂Vuex 源码整体架构从Vue.use(Vuex)插件安装、new Vuex.Store构造函数到state响应式原理与commit、dispatch等核心 API 的实现。掌握这套思路你不仅能秒懂 Vuex 原理、应对面试还能亲手打造属于自己的状态管理库。想动手调试的读者可以先克隆本仓库完整源码解析都放在了docs/vuex/目录下仓库地址https://gitcode.com/gh_mirrors/blog51/blog配合 vuex-store-instance.xmind 思维导图一起看事半功倍。一、为什么 Vuex 源码值得读千行代码的状态管理库Vuex 源码打包后只有一千多行规模小、结构清晰是学习状态管理原理的最佳入口 。它的核心设计只有三点$store注入通过全局Vue.mixin在每个 Vue 实例的beforeCreate生命周期中注入同一个Store实例所以组件里才能用this.$store.dispatchstate响应式本质是用new Vue()包了一层私有实例state挂在其_data.$$state上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 的一个插件包含install、Store、mapState等 4 个辅助函数安装流程非常简单Vue.use(Vuex)触发install(_Vue)函数vuex/src/store.js记录Vue引用然后调用applyMixinapplyMixinvuex/src/mixin.js执行Vue.mixin({ beforeCreate: vuexInit })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._actions、this._mutations、this._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五、resetStoreVMVuex 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 核心 APIcommit 与 dispatch 的实现Store 原型上还有commit、dispatch、replaceState、watch、subscribe、registerModule等 API核心两个的实现思路如下commit提交 mutationcommit (_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 等辅助函数组件绑定的「语法糖」mapState、mapGetters、mapMutations、mapActions都位于vuex/src/helpers.js实现套路高度统一normalizeNamespace统一命名空间没传就置空末尾补/normalizeMap把数组/对象统一成[{ key, val }]数组为每一项生成一个计算属性或方法从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 →resetStoreVM用new Vue实现响应式 →commit/dispatch/watch等 API 围绕store._vm服务 →mapState等辅助函数抹平命名空间差异。几个调试建议来自作者的实战经验先看官方 Shopping Cart 例子vuex/examples/shopping-cart把store实例在控制台打印出来对照关系图看给 webpack 配置加上devtool: source-map然后在 Chrome 断点调试installModule、resetStoreVM两个函数即可串起主线会调试代码看源码就简单了——不调试只看文章很难消化吸收。Vuex 源码虽然只有千行出头但把「响应式、模块化、插件化、订阅者模式」这些状态管理库的通用设计全用到了。读懂它之后再去看本仓库中 Vuex 4Vue 3 下基于provide/inject的docs/vuex4/README.md、Reduxdocs/redux/README.md等系列分析你会发现它们不过是同一套架构的不同变体。动手照着本文的链路写一个玩具版状态管理库你离「源码大神」又近了一步 。【免费下载链接】blog若川的博客—撰写了学习源码整体架构系列几十篇。组织了源码共读活动每周一起学习200行左右的源码加我微信 ruochuan02 参与。项目地址: https://gitcode.com/gh_mirrors/blog51/blog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考