VUEX 3.x源码分析——3. 理解Mutations

这是对vuex3.x版本的源码分析。
本次分析会按以下方法进行:

  1. 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。
  2. 在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。
  3. 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。

本章只讲解vuex中的Mutations,这也是vuex官网中“核心概念”的第三个。
想了解vuex中的其他源码分析,欢迎参考我发布的下列文章:
VUEX 3 源码分析——1. 理解State
VUEX 3 源码分析——2. 理解Getter
VUEX 3 源码分析——3. 理解Mutations
VUEX3 源码分析——4. 理解Action
VUEX 3 源码分析——5. 理解Module
VUEX 3 源码分析——6. 理解命名空间namespace
VUEX 3 源码分析——7. 模块的动态注册和卸载

初始化

  • 在 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} // 给外层赋值解构用
}

至此,可以对官网中的所有内容做相关解释:

  1. 为什么mutation有两种提交方式:
    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} // 给外层赋值解构用
}
  1. 为什么mutation必须是同步函数
    mutation在内部是没有针对异步函数做特殊处理,如常见的async-awailt promise等。
  2. 为什么只能使用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的实现源码。

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值