VUEX 3.x源码分析——3. 理解Mutations
这是对vuex3.x版本的源码分析。
本次分析会按以下方法进行:
-
按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。
-
在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。
-
每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。
本章只讲解vuex中的Mutations,这也是vuex官网中“核心概念”的第三个。
初始化
-
在 installModule 函数中,将每个定义的mutation按key-value遍历,存入store._mutations中.
-
注意这里的每个store._mutations[k]都是一个数组,数组中的元素是一个函数(wrappedMutationHandler,接收payload参数),此函数负责执行mutation。
// store-util.js export function installModule(store, rootState, path, module, hot) { ... // forEachMutation方法和getter中的forEachGetter类似 module.forEachMutation((mutation, key) => { const namespacedType = namespace + key registerMutation(store, namespacedType, mutation, local) }) } function registerMutation(store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []) //短路求值 entry.push(function wrappedMutationHandler(payload) { handler.call(store, local.state, payload) }) }
commit的调用
-
在Store类上,定义了commit方法。
-
调用commit方法后,按传入的参数_type从store._mutations[type]中取出所有的执行函数
-
调用_withCommit方法依次执行mutation函数
// store.js export class Store { ... commit(_type, _payload, _options) { const {type, payload, options} = unifyObjectStyle(_type, _payload, _options) const mutation = {type, payload} const entry = this._mutations[type] if (!entry) {...} // _withCommit 是添加了额外的逻辑,这里先不管具体细节,等于执行entry.forEach this._withCommit(() => { entry.forEach(function commitItertor(handler) { handler(payload) }) }) } } // store-util.js export function unifyObjectStyle(type, payload, options) { if (isObject(type) && type.type) { options = payload payload = type type = type.type } if (__DEV__) {assert(...)} return {type, payload, options} // 给外层赋值解构用 }
至此,可以对官网中的所有内容做相关解释:
-
unifyObjectStyle 函数处理了两种提交逻辑
store.commit({ type: 'increment', amount: 10 }) mutations: { increment (state, payload) { state.count += payload.amount } } // store-util.js export function unifyObjectStyle(type, payload, options) { if (isObject(type) && type.type) { options = payload payload = type type = type.type } if (__DEV__) {assert(...)} return {type, payload, options} // 给外层赋值解构用 }
-
为什么mutation必须是同步函数
mutation在内部是没有针对异步函数做特殊处理,如常见的async-awailt promise等。 -
为什么只能使用commit的形式提交mutation,而不是直接调用mutation来提交:
store初始化时没有mutation属性,只有一个_mutations数据结构,这个数据并不执行mutation,只是存储相关的函数。
另外,我们还能通过源码发现一些官网没有写出,但实际存在的功能:
- 在执行mutation时制定了上下文为store,所以必然可以使用getter和其他内容,
// store-util.js function registerMutation(store, type, handler, local) { const entry = store._mutations[type] || (store._mutations[type] = []) //短路求值 entry.push(function wrappedMutationHandler(payload) { // hander 就是我们定义的某个mutation,它使用了call指定上下文为store handler.call(store, local.state, payload) }) }
如何开发一个mutation的订阅插件
-
在执行commit时,将对应的mutation执行完毕后,还执行了相关的订阅函数。
-
store定义了subscribe方法,方便用户来订阅某些内容,执行一些额外的操作,比如日志记录、时间旅行调试等。
-
genericSubscribe 函数主要有两个功能:1. 将用户定义的额外操作(即fn)加入到this._subscribers中。2.返回一个函数,这个函数的作用是取消订阅,即将fn从this._subscribers删除。
// ./store.js export class Store { ... constructor(options = {}) { this._subscribers = [] } commit(_type, _payload, _options) { const {type, payload, options} = unifyObjectStyle(_type, _payload, _options) const mutation = {type, payload} const entry = this._mutations[type] if (!entry) {...} this._withCommit(() => { entry.forEach(function commitItertor(handler) { handler(payload) }) }) this._subscribers.slice().forEach(sub => sub(mutation, this.state)) } subscribe(fn, options) { return genericSubscribe(fn, this._subscribers, options) } } // store-util.js export function genericSubscribe (fn, subs, options) { if (subs.indexOf(fn) < 0) { options && options.prepend ? subs.unshift(fn) : subs.push(fn) } return () => { const i = subs.indexOf(fn) if (i > -1) { subs.splice(i, 1) } } }
-
了解了上述源码后,就可以开发一个订阅mutation的插件
-
这里也解释了为什么vuex不建议直接修改state的内容,而是通过mutation来修改。因为vuex有订阅的功能,方便用户开发额外功能或者测试调试。而如果指定修改state则不会触发这些功能。
const createLoggingPlugin = (mutationType) => { return (store) => { store.subscribe((mutation, state) => { if (mutation.type === mutationType) { console.log(111); } }) } } // 使用插件 const store = new Vuex.Store({ ... plugins: [createLoggingPlugin('increment')] })
以上就是官网上Mutation的相关源码,下一遍会分析Mutations的实现源码。
