【源码解读】vuex原理2,new Store时做了哪些事,commit又是如何把字符串变成方法调用的

126 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情

前言

大家好,上一次为每个组件添加store属性分享中我们解析了vuex是如何为每个组件的实例挂载$store属性的,那么这也仅仅是Vuex初始化时的一个过程,下面还有一些关于mutations,actions,modules等等相关的操作。接下来的分享中我们将继续解读Vuex的源码,探秘mutations、actions和modules等各个模块是如何工作的。

new Store()都干了啥

本次分享还是基于vuex3.2版本进行源码解读

上篇分享中我们提到Vuex是一个包含了install方法和Store类的对象,并且对install方法的实现原理进行了源码分析,解析来还剩下一个vuex中的核心模块 - Sotre类,那么我们就先来看下在new Store的时候都做了写什么事

constructor (options = {}) {
    // store internal state
    this._committing = false
     ...
    this._makeLocalGettersCache = Object.create(null)

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    const state = this._modules.root.state
    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state)
    // apply plugins
    plugins.forEach(plugin => plugin(this))
  }

以上是Store类构造函数中的部分主要代码,在该构造函数中主要做了如下这么几件事:

  • 给store实例(this)挂载一些_xxx的变量供Store内部使用,主要用于存储一些内部状态
  • 给store实例(this)本身绑定dispatch和commit方法,而在该方法中最终调用的是Store原型对象上对应的方法
    • const { dispatch, commit } = this这句代码中实际上获取的是Store原型上对应的两个方法,因为这个时候this(store实例本身)上还没有这两个方法
    • 通过this.commit = xxx 和 this.dispatch = xxx来给store实例本身也添加两个对应的方法,但在方法内部最终执行的还是原型上的方法
  • 调用installModule安装模块,因为如果项目比较大的话,一般不会将所有的状态都放在同一个vuex模块中管理,而是拆分成不同的模块,这种情况下就需要将所有的模块都需要安装注册。从注释来看该方法主要做了三件事:
    • 初始化根模块
    • 递归注册所有子组件
    • 收集所有模块的getters放到当前实例的_wrappedGetters中
  • 调用resetStoreVM方法,该方法主要用于:
    • 初始化store实例上的vm,也就是给store实例上也挂载一个Vue的实例
    • 把上面的_wrappedGetters 注册为一个计算属性
  • 最后安装插件 从源码上来看,我们在new Store的时候就做了以上这么几件事,那么其核心内容还是在installModuleresetStoreVM中。

installModule

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  ...

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
     ...
      Vue.set(parentState, moduleName, module.state)
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)

  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

以上是installModule的主要代码,其主要做了三件事:

  • 命名空间的检查(该部分代码已省略),默认情况所有的模块都是定义在全局命名空间中的,但vuex也允许我们使用单独的命名空间,所以这里会做个命名空间的处理
  • 如果当前模块不是根模块,则会调用store实例的_withCommit函数并传递一个新函数,其目的就是让这个新函数在_withCommit中执行。
    • 在该新函数体中其核心操作就是通过调用Vue的set方法,将子模块中的state分别添加的根模块的state中,并且让这些state也是响应式
  • 接下来第三件事就是注册各个模块中的mutation、action和getter以及安装子模块
    • registerMutation注册mutation
    • registerAction注册action
    • registerGetter注册getter
    • installModule按钮子模块

registerMutation

function registerMutation(store, type, handler, local){
    var entry = store._mutations[type] || (store._mutations[type] = []);
    entry.push(function wrappedMutationHandler(payload){
        handler.call(store, local.state, payload)
    })
}

registerMutation方法比较简单,核心功能就是向entry数组中添加一个wrappedMutationHandler函数

  • registerMutation方法接收4个参数分别是:
    • store: Store的实例
    • type:type就是我们在mutation对象中定义的同步函数的函数名
    • handler:handler则是mutation对象中对应同步函数的定义,如 function xxx(){xxx}
    • local:local是一个包含了dispatch、commit、getters和state的对象
  • 第一句先定义一个变量entry,然后根据方法名称(type)到实例对象的_mutations中查找是否存在,如果存在将对应值赋值给entry,否则赋值一个空数组给entry
    • 说明:_mutations是在new Store时定义的一个空对象,因此初次进来时_mutations还是空的,肯定找不到type对应的值,所以这里entry会被赋值为一个空数组
  • 然后通过push向数组entry中添加一个一个函数wrappedMutationHandler,用于执行我们在mutation对象中定义的同步方法 到此就完成了mutation的注册,那了个么当我们通过this.$store.commet()去提交一个方法的时候,在它的背后又是如何处理的呢,接下来我们就再来挖一挖commit的源码。

commit

commit (_type, _payload, _options) {
    // check object-style commit
    const { type, payload, options } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    //...省略部分校验代码
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    //...省略部分校验代码
 }
  
function unifyObjectStyle(type, payload, options){
    if(isObject(type) && type.type){
        options = payload
        payload = type
        type = type.type
    }
    return {type, payload, options}
}

在Store的原型对象上有这么个commit方法,该方法就是我们常用的this.$store.commit中的commit方法,我们来看看它都做了哪些事。

  • 首先该函数接收三个参数:
    • _type: 对应的是mutation对象中的方法名(字符串类型)
    • _payload:一般情况下是一个对象,该对象中包含了我们调用mutation中的方法时要传递的参数
    • _option:额外的参数,我们在调用commit时一般好像用不到
  • 函数体内的第一句代码调用了个unifyObjectStyle方法,又从该方法中重新解析出了对应的三个参数,其中还给出注释说:check object-style commit,那这个玩意是干嘛的呢?其实说到这里就不得不提一句关于commit调用时传递参数的方式了。commit在调用时有两种传参的方式:
    • 第一种就是第一个参数为字符串,第二个参数是对象的形式传递,也是我们通常用的比较多的一种方式,如
    this.$store.commit('changeName',{name:'西瓜watermelon'})
    
    • 另外一种方式则是传递一个包含了type对象,所有参数包括type都放在对象中,如
    this.$store.commit({type:'changeName',name:'西瓜watermelon'})
    
    因此这里的unifyObjectStyle方法就是为了把对象中的方法名(type)跟具体的参数区分开来
  • 接下来就是根据方法名(type)从_mutations对象中找到对应的值,其值是一个数组(在上一步我们讲注册mutation时保存进去的)而数组中保存的是一个名为wrappedMutationHandler的函数
  • 最后就是调用Store实例上的_withCommit方法,该方法接收一个匿名函数作为实参,而在该匿名函数中会遍历entry数组中的所有方法,并让方法执行
    • 另外,该匿名函数会在_withCommit中被调用(前面也有提到过),该匿名函数执行的时候实际上就是让entry数组中的方法执行,而数组中的方法执行实际上调用的就是wrappedMutationHandler,让wrappedMutationHandler执行,而这个方法的内部最终调用的则是我们在mutation对象中定义的那个真正的改变state值的方法

总结

本次分享中我们主要解读了new Store、registerMutation和commit方法的源码,简单概括一下就是:

在new Store时会初始化一堆实例属性,然后就是通过installModule安装模块,在安装模块的时候会注册mutation、action、getter等,那么在注册mutation时会向数组entry中添加一个wrappedMutationHandler函数,而在该函数内部调用真正的我们在mutation对象中定义的方法,然后再把该数组保存在一个以方法名为属性的对象_mutation中,当我们外部调用commit的时候就会从该对象中找出对应方法名的数组,最后遍历该数组让数组中的wrappedMutationHandler执行,进而让我们定义的方法执行来改变state中的值

本次分享就到这里了,喜欢的朋友欢迎点个赞哦!