Vuex 4 入门详解:Vue 3 应用中的集中式状态管理
Vuex 4 入门详解Vue 3 应用中的集中式状态管理【免费下载链接】vuex️ Centralized State Management for Vue.js.项目地址: https://gitcode.com/gh_mirrors/vu/vuex本文以 Vuex 官方文档的“什么是 Vuex”一章docs/ja/index.md该文档面向基于 Vue 3 运行的 Vuex 4为核心系统讲解 Vuex 作为“状态管理模式 库”的基本理念、单向数据流的三要素、共享状态为何必须集中管理并结合当前仓库源码src/store.js、src/store-util.js验证这些理念在 Vuex 4 中的具体落地方式。读完本文你将理解 Vuex 的设计动机、Store 的创建与响应式原理、commit/dispatch 的同步异步边界并能在中大型 Vue 3 应用中做出是否引入 Vuex 的合理判断。需要先说明版本前提当前仓库的版本为4.1.0其peerDependencies要求vue^3.2.0见 package.json即本文全部结论适用于Vuex 4 Vue 3组合面向 Vue 2 的 Vuex 3 是另一套文档与 API不在本文讨论范围内。Vuex 是什么一句话定义Vuex 是 Vue.js 应用的状态管理State Management模式 库。它通过强制两条规则来实现可预测性状态只能通过预测可追踪的方式被改变即必须经由同步的 mutation 修改它充当应用中所有组件共享的集中式 Store。从源码结构看Vuex 对外导出的 API 面非常克制。入口文件 src/index.js 只导出 9 个成员API类型作用Store/createStore类 / 工厂函数创建 Store 实例useStore/storeKey函数 / SymbolComposition API 下注入与获取 StoremapState/mapMutations/mapGetters/mapActions辅助函数把 Store 内容映射为组件的 state/methodscreateNamespacedHelpers辅助函数为命名空间模块批量生成映射辅助函数createLogger插件开发环境记录 mutation 日志见 src/plugins/logger.js也就是说所谓“状态管理库”在代码层面就是createStore返回的Store实例外加一组帮助你在组件中消费 Store 的工具函数。状态管理模式的三要素从一个计数器开始理解 Vuex 之前先看一个不依赖任何库的极简 Vue 计数器const Counter { // state data () { return { count: 0 } }, // view template: div{{ count }}/div , // actions methods: { increment () { this.count } } } createApp(Counter).mount(#app)这个组件内部其实包含了状态管理的三个基本要素状态State驱动应用的可信信息源the source of truth视图View对状态的一种声明式映射动作Action响应视图中的用户输入从而触发状态变更的方式。它们共同构成“单向数据流”这一概念的最小责任划分数据只能沿 State → View → Action → 修改 State 的方向流动视图不直接写状态用户输入通过 action 间接落到状态上。这一约定让数据变化可追踪、可复现。共享状态为何需要集中管理单向数据流的简洁性在多个组件需要共享同一状态时立刻遭遇瓶颈典型场景有两种多个视图依赖同一状态若只用 props 向下传递在深层嵌套的组件树里逐层传 props 非常繁琐而且对兄弟组件完全不适用props 只能自上而下不同视图的动作需要修改同一状态常见“土办法”是直接引用父/兄弟组件实例或通过事件在多份状态副本之间手动同步——这些模式都很脆弱很快就会演变成难以维护的代码。解决思路是把共享状态从组件中抽取出来交给一个全局单例管理组件树整体变成一个巨大的“视图”任意深度的组件都能访问同一份状态任何组件都能触发修改状态的动作同时把状态管理相关的概念state / mutation / action / getter显式地定义、分离并施加规则代码结构与可维护性随之提升。这正是 Vuex 的基本思想。它的理念明显受到 Flux、Redux 和 The Elm Architecture 的影响但与其他通用模式的关键差异在于Vuex 是专门为 Vue.js 的细粒度响应式系统调优过的库——它借用 Vue 的依赖追踪与更新调度来实现高效更新而不是像部分 Flux 系框架那样依赖整体重绘。这一点可以在源码中得到直接印证见下一节。源码印证Vuex 4 如何实现这套理念1. 创建 StorecreateStore只是一个语法糖src/store.js 中export function createStore (options) { return new Store(options) }Store构造函数src/store.js#L19-L76完成四件事恰好对应状态管理模式的几个关注点constructor (options {}) { const { plugins [], strict false, devtools } options // ① 内部表mutation / action / getter 注册表、模块树、订阅者列表 this._mutations Object.create(null) this._actions Object.create(null) this._wrappedGetters Object.create(null) this._modules new ModuleCollection(options) // 模块树 ... // ② 绑定 commit / dispatch保证以 store 为 this 调用 this.dispatch function boundDispatch (type, payload) { ... } this.commit function boundCommit (type, payload, options) { ... } // ③ 初始化根模块递归注册所有子模块与 getter installModule(this, state, [], this._modules.root) // ④ 建立响应式状态 计算 getter然后逐个应用插件 resetStoreState(this, state) plugins.forEach(plugin plugin(this)) }注意commit与dispatch在构造时就被包装为始终指向该 store 实例的绑定函数这意味着你可以在组件里把this.$store.commit直接解构传给其他模块而不会丢失上下文。2. 状态响应式把整棵 state 树包进reactive“利用 Vue 的响应式系统”这句宣传语落在 src/store-util.js#L60-L62store._state reactive({ data: state })整个 state 树被包进 Vue 3 的reactive代理对象Store.state的 getter 再返回this._state.datasrc/store.js#L91-L93。由此带来两个行为特征细粒度更新组件只读state.count时只订阅该属性count只触发依赖它的视图更新这正是文档所说“为高效更新而调优”的实现基础禁止直接赋值替换Store定义了set state (v)开发环境下直接执行store.state xxx会断言失败提示改用store.replaceState()src/store.js#L95-L99、L215-L219——保证状态只能作为整体树被受控地替换而不是被悄悄换掉引用。此外strict: true选项会在开发环境注册一个deep: true, flush: sync的 watchersrc/store-util.js#L271-L277只要 state 在非 mutation 期间_committing false被修改就断言报错“do not mutate vuex store state outside mutation handlers”。这是“只能通过预测可追踪方式改变状态”这条规则的运行时强制。3. Getters用computedEffectScope实现缓存派生状态Getter 本质是带缓存的派生状态。src/store-util.js#L42-L58 显示每个 getter 被包成 Vue 的computedconst scope effectScope(true) // detached scope scope.run(() { forEachValue(wrappedGetters, (fn, key) { computedObj[key] partial(fn, store) computedCache[key] computed(() computedObj[key]()) Object.defineProperty(store.getters, key, { get: () computedCache[key].value, enumerable: true }) }) })这里有个值得注意的工程细节computed被创建在一个detached EffectScopeeffectScope(true)中。源码注释解释得很清楚——如果不这样做getter 的 effect 会跟随某个组件的作用域组件卸载时 computed 就会被销毁导致其他组件再访问该 getter 时失效。Vuex 特意用独立作用域托管 getter保证它们的生命周期与 Store 一致。4.commit与dispatch同步与异步的边界这是 Vuex 规则体系的核心实现对比两个方法可以看出边界设计commit(type, payload)src/store.js#L101-L136同步查表this._mutations[type]未注册的类型在开发环境打console.error后静默返回随后在_withCommit包裹内逐个执行 handler最后通知subscribe的订阅者。整个流程没有 Promise保证了 mutation 的同步性与可记录性dispatch(type, payload)src/store.js#L138-L197查this._actions[type]支持多个同名 action handler 并行执行entry.length 1时用Promise.all归并并始终返回 Promise——action handler 无论返回什么都会被包成 Promise见 src/store-util.js#L240-L242因此 action 天然适合承载异步操作同时暴露了subscribeAction的before/after/error三个钩子形成对 action 全生命周期的观测点。action handler 收到的上下文由 src/store-util.js#L229-L252 构造包含模块级的dispatch、commit、state、getters以及全局的rootState、rootGetters——这就是后文模块示例中解构参数({ commit, state })的来源。5. 注入应用app.use(store)背后发生了什么install (app, injectKey) { app.provide(injectKey || storeKey, this) // inject 注入 app.config.globalProperties.$store this // Options API 的 this.$store const useDevtools this._devtools ! undefined ? this._devtools : __DEV__ || __VUE_PROD_DEVTOOLS__ // 默认开发环境启用 DevTools if (useDevtools) { addDevtools(app, this) } }src/store.js#L78-L89即一次app.use(store)同时打通了 Options APIthis.$store与 Composition APIinject(storeKey)/useStore()两条消费路径并在默认情况下挂载vue/devtools-api驱动的 DevTools 集成——这也是 package.json 中唯一运行时依赖vue/devtools-api的用途。最小可用示例仓库内置的计数器上述原理在仓库的示例中都能直接对应。examples/classic/counter/store.js 创建了一个完整 Storeimport { createStore } from vuex // 根 state 对象每个 Vuex 实例就是一棵单一 state 树 const state { count: 0 } // mutations真正修改状态的操作必须是同步的可被插件记录用于调试 const mutations { increment (state) { state.count }, decrement (state) { state.count-- } } // actions可产生副作用、可包含异步操作的函数 const actions { increment: ({ commit }) commit(increment), decrement: ({ commit }) commit(decrement), incrementIfOdd ({ commit, state }) { if ((state.count 1) % 2 0) { commit(increment) } }, incrementAsync ({ commit }) { return new Promise((resolve, reject) { setTimeout(() { commit(increment) resolve() }, 1000) }) } } // getters派生状态的纯函数 const getters { evenOrOdd: state state.count % 2 0 ? even : odd } export default createStore({ state, getters, actions, mutations })组件侧的接入只有三步examples/classic/counter/app.jsimport { createApp } from vue import Counter from ./Counter.vue import store from ./store const app createApp(Counter) app.use(store) // 触发 install()provide 挂载 $store DevTools app.mount(#app)对照源码可以看出示例与实现的精确对应关系incrementAsync里的setTimeout正是 action 承载异步、mutation 保持同步的分工({ commit, state })解构的就是registerAction注入的上下文而evenOrOdd这个 getter 最终会以computed缓存的形式挂在store.getters上。仓库中还有 Composition API 风格的同款示例examples/composition/counter/store.js以及更复杂的购物车、聊天示例可作为进一步阅读的路径。何时该用 Vuex官方文档给出了清醒的判断标准Vuex 能帮你管理共享状态但也要付出概念和样板代码的成本这是短期生产力与长期生产力的权衡。如果你的应用规模不大、没有中大型 SPA 级别的复杂度引入 Vuex 只会显得冗长很多时候一个手写的简单 store 模式组件外部的 reactive 对象 修改函数就够了不需要引入完整库如果你正在构建中大型 SPA组件外状态开始难以组织多组件读写同一数据、跨层级更新、调试困难Vuex 就是下一步的合理选择。Redux 的作者 Dan Abramov 有一句被文档引用的经典评价Flux 库就像眼镜一样当你发现自己需要它们的时候你自然就会知道。把它翻译成工程决策语言就是不要为了用而用而是当“多份状态副本手动同步”“props 层层透传”“组件间直接引用”这类坏味道反复出现时集中式 Store 的抽象收益才会超过它的样板成本。同时要注意 Vuex 4 的适用前提它运行在 Vue 3 上peerDependencies: vue^3.2.0其install(app)注入方式、useStore/storeKey的 Composition API 支持、vue/devtools-api集成都是 Vue 3 生态的产物如果你维护的是 Vue 2 项目应使用 Vuex 3 及其对应文档两套 API 存在不兼容差异可参考仓库文档中的迁移章节 docs/ja/guide/migrating-to-4-0-from-3-x.md。小结回顾本文从 docs/ja/index.md 展开的完整脉络概念层Vuex 状态管理模式 库三要素State / View / Action构成单向数据流规则保证变更可预测动机层props 透传与事件同步在共享状态下失效抽取为全局单例 Store 是结构化解法实现层仓库源码证据state 树被reactive包装获得细粒度响应式src/store-util.js#L60-L62getter 由computed 独立EffectScope承载src/store-util.js#L42-L58commit同步、dispatch异步返回 Promisesrc/store.js#L101-L197app.use(store)一次打通 Options API 与 Composition API 两条消费路径src/store.js#L78-L89决策层Vuex 的价值与项目规模正相关按“眼镜理论”在共享状态复杂度超过手写方案可承受范围时引入。掌握这条“理念 → 动机 → 实现 → 决策”的链条后继续阅读仓库中的状态docs/ja/guide/state.md、mutationdocs/ja/guide/mutations.md、模块docs/ja/guide/modules.md等后续章节时每个概念都能在当前仓库的源码中找到落点。【免费下载链接】vuex️ Centralized State Management for Vue.js.项目地址: https://gitcode.com/gh_mirrors/vu/vuex创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →