
Vuex Mutations 完全指南Commit 提交机制、载荷传参与同步性原则【免费下载链接】vuex️ Centralized State Management for Vue.js.项目地址: https://gitcode.com/gh_mirrors/vu/vuex本文围绕 Vuex 的核心概念Mutation变更展开讲解如何通过store.commit提交 mutation 来修改 Store 中的 state涵盖载荷payload传参、对象风格提交、常量定义类型、同步性约束、组件内mapMutations辅助函数等完整实践并结合 vuex 仓库源码src/store.js、src/store-util.js、src/helpers.js、src/plugins/devtool.js揭示底层实现原理。读完本文你将掌握 Vuex 中修改 state 的唯一正确姿势并能用 devtools 对每次状态变更进行可靠追踪与调试。什么是 Mutation改变 State 的唯一途径在 Vuex 中唯一能够真正改变 state 的方式就是提交commit一个 mutation。mutation 在形态上非常接近事件每个 mutation 拥有一个字符串type类型和一个handler处理函数。handler 是真正执行状态修改的地方它会将 state 作为第一个参数接收import { createStore } from vuex const store createStore({ state: { count: 1 }, mutations: { increment (state) { // 在这里修改 state state.count } } })注意你不能直接调用 mutation 的 handler 函数。它更像是一种事件注册当类型为increment的 mutation 被触发时调用对应的 handler。要触发它必须使用store.commit并传入其类型store.commit(increment)从源码看commit是Store类上定义的公开方法src/store.js#L101-L136其工作流程大致为通过unifyObjectStyle统一不同风格的参数字符串风格与对象风格从this._mutations[type]查找注册的处理函数条目若不存在开发环境下会输出[vuex] unknown mutation type: ${type}错误并直接返回在_withCommit包裹下依次执行所有 handler传入 payload通知_subscribers订阅者devtools 与插件依赖这一机制。mutation 的注册发生在 Store 初始化阶段installModule遍历每个模块的mutations定义通过registerMutationsrc/store-util.js#L222-L227将其包装为wrappedMutationHandler(payload)存入store._mutations包装函数内部会以handler.call(store, local.state, payload)的形式把模块局部 state与 payload 传给原始 handler。此外构造函数中还有一步关键绑定src/store.js#L50-L58commit被重新包装为boundCommit从而保证解构或传递commit方法时this始终指向 store 实例。带载荷提交Commit with Payloadstore.commit可以接收额外的参数这个参数被称为 mutation 的payload载荷mutations: { increment (state, n) { state.count n } }store.commit(increment, 10)在大多数场景下payload 应当是一个对象这样它既能承载多个字段也能让 devtools 中记录的 mutation 信息更具可读性、描述性mutations: { increment (state, payload) { state.count payload.amount } }store.commit(increment, { amount: 10 })单元测试test/unit/store.spec.js#L9-L22印证了这一行为定义[TEST] (state, n) { state.a n }后执行store.commit(TEST, 2)断言store.state.a变为 3。同时测试还覆盖了类型校验若传入非字符串类型unifyObjectStyle会断言失败并抛出expects string as the type, but found undefinedtest/unit/store.spec.js#L42-L59。对象风格提交Object-Style Commit提交 mutation 的另一种方式是直接传入一个带有type属性的对象store.commit({ type: increment, amount: 10 })使用对象风格提交时整个对象会被作为 payload 传给 mutation handler因此 handler 的写法保持不变mutations: { increment (state, payload) { state.count payload.amount } }这一转换的底层逻辑在unifyObjectStyle中实现src/store-util.js#L283-L295当第一个参数是对象且包含type属性时将type提取出来原对象整体降级为payload。对象风格同样被单元测试覆盖test/unit/store.spec.js#L24-L40且它对 action 的dispatch同样生效——commit与dispatch共用同一个unifyObjectStyle工具函数保证两种提交方式在两类操作上行为一致。使用常量定义 Mutation 类型在各类 Flux 实现中用常量来定义 mutation 类型是常见做法。它带来两个直接收益代码可以利用 linter 等工具做静态检查避免字符串拼写错误将所有常量集中在一个文件中协作成员可以一眼览尽整个应用支持的全部 mutation。// mutation-types.js export const SOME_MUTATION SOME_MUTATION// store.js import { createStore } from vuex import { SOME_MUTATION } from ./mutation-types const store createStore({ state: { /* ... */ }, mutations: { // 使用 ES2015 的计算属性名computed property name特性 // 将常量作为函数名 [SOME_MUTATION] (state) { // 修改 state } } })是否使用常量很大程度上取决于团队偏好在大型项目、多人协作时它很有价值但如果你不喜欢这种写法它完全是可选的。仓库自身的测试同样遵循这一模式——test/unit/store.spec.js 中统一以const TEST TEST定义类型常量再通过[TEST]计算属性名注册 mutation既避免了魔法字符串也让测试用例更加健壮。Mutation 必须是同步的有一条必须牢记的硬性规则mutation 的 handler 函数必须是同步的。为什么看下面的反例mutations: { someMutation (state) { api.callAsyncMethod(() { state.count }) } }假设我们在调试应用并查看 devtools 的 mutation 日志每记录一条 mutationdevtools 都需要捕获 state 的变更前before与变更后after快照。然而上例中的异步回调破坏了这一前提——mutation 被提交时回调尚未执行devtools 也无法得知回调究竟何时会被调用因此在回调中执行的任何状态修改本质上都是不可追踪的从源码可以验证 devtools 的追踪机制src/plugins/devtool.js#L81-L102devtools 通过store.subscribe((mutation, state) {...})订阅 mutation每当commit完成就会在vuex:mutations时间线层记录一条带时间戳、mutation 类型与完整 state 快照的事件。一旦 mutation 内部掺杂异步逻辑commit返回时刻的 state 快照就无法反映真实的状态变迁before/after 对比随之失效。此外同步约束也是strict严格模式能够工作的前提enableStrictModesrc/store-util.js#L271-L277通过watch深度监听 state一旦发现在_committing标志为 false 的情况下 state 被修改即修改发生在 mutation handler 之外就会断言报错do not mutate vuex store state outside mutation handlers。而_committing标志正是由commit内部的_withCommitsrc/store.js#L265-L270在同步调用 handler 期间置为 true 的——这一机制天然依赖 handler 的同步性。那么异步操作放哪里答案是Actions动作见 docs/guide/actions.md。Actions 可以包含任意异步逻辑并在适当的时候回头调用commit来提交同步的 mutation。在组件中提交 Mutation在 Vue 组件中有两种方式提交 mutation方式一直接使用this.$store.commit通过this.$store.commit(xxx)直接提交前提是在应用入口通过app.use(store)完成 store 注入见 src/store.js#L78-L89 的install方法它会将 store 挂载到app.config.globalProperties.$store。方式二使用mapMutations辅助函数mapMutations将组件方法映射为store.commit调用需要根 store 注入import { mapMutations } from vuex export default { // ... methods: { ...mapMutations([ increment, // 将 this.increment() 映射为 this.$store.commit(increment) // mapMutations 也支持传递 payload incrementBy // 将 this.incrementBy(amount) 映射为 this.$store.commit(incrementBy, amount) ]), ...mapMutations({ add: increment // 将 this.add() 映射为 this.$store.commit(increment) }) } }数组形式适用于方法名与 mutation 类型完全一致的简洁场景对象形式则允许为方法起别名键为组件方法名值为 mutation 类型。其底层实现在 src/helpers.js#L42-L64核心逻辑是mapMutations把传入的数组或对象标准化为{ key, val }列表为每个 key 生成一个包装函数调用时执行commit.apply(this.$store, [val].concat(args))——即把组件方法收到的所有参数原样追加到 mutation 类型之后这正是this.incrementBy(amount)能自动把amount作为 payload 传递的原因。仓库测试test/unit/helpers.spec.js#L110-L165验证了两种形式还额外覆盖了两种更高级的用法函数形式对象值可以是函数此时commit作为第一个参数注入函数可自定义提交逻辑methods: mapMutations({ plus (commit, amount) { commit(inc, amount 1) } }) // 调用 this.plus(42) 最终提交 inc载荷为 43命名空间形式mapMutations(foo, {...})将提交自动绑定到foo/命名空间模块对应 src/helpers.js#L50-L57 中module.context.commit的解析逻辑。在命名空间下mutation 类型会被自动加上模块前缀——这也是registerMutation使用namespace key注册类型src/store-util.js#L119-L122的必然结果。若传入的 mapper 既不是数组也不是对象mapMutations会在开发环境输出[vuex] mapMutations: mapper parameter must be either an Array or an Object错误见 test/unit/helpers.spec.js#L217-L237 的验证。从 Mutation 到 Action同步事务的边界异步逻辑与状态修改交织会让程序变得极难推理例如同时调用两个方法它们都包含异步回调并修改 state你很难判断回调实际被调用的时机与先后顺序。这正是 Vuex 将二者概念分离的根本原因——mutation 是同步事务synchronous transactionstore.commit(increment) // increment mutation 可能引起的任何 state 变化 // 都应当在当前这一时刻完成也就是说当store.commit返回时所有由该 mutation 引发的状态变更已经发生完毕。结合上文源码可知commit在_withCommit内同步执行全部 handler 后才通知订阅者不存在任何挂起中的异步中间态。要处理异步操作请引入 Actions它们可以自由执行异步代码如请求接口随后通过commit提交同步的 mutation 来落地状态变更。Actions 的完整用法与mapActions见 docs/guide/actions.md。如果你希望进一步约束状态只能在 mutation 中修改可以开启严格模式docs/guide/strict.md而在多模块的大型应用中命名空间下的 mutation 提交规则可参考 docs/guide/modules.md。小结Mutation 使用要点修改 state 只能通过store.commit提交 mutationhandler 接收(state, payload)支持字符串风格commit(type, payload)与对象风格commit({ type, payload })两种提交形式可用常量集中管理 mutation 类型借助 ES2015 计算属性名注册便于 lint 与全局检索handler必须同步异步操作交给 Actions否则 devtools 无法生成可靠的 before/after 快照严格模式也无法兜底组件内可通过this.$store.commit或mapMutations数组 / 对象 / 函数 / 命名空间形式提交。【免费下载链接】vuex️ Centralized State Management for Vue.js.项目地址: https://gitcode.com/gh_mirrors/vu/vuex创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考