Vuex4.0 源码分析

932 阅读12分钟

本文适合摸过vue3和vuex4的兄弟,除了devtools外其余代码都有逐行的分析,新手也看得懂,如果分析得有什么不对的,欢迎大佬们评论,废话不多说,直接开搞。vuex4.0.2源码下载地址

Store

// src/store.js 15-70
export function createStore (options) {
  return new Store(options)
}

export class Store {
  constructor (options = {}) {
    if (__DEV__) {// 这里会被编译为↓这段代码,当前环境为开发环境时,就会执行代码块中的代码
    //if ((process.env.NODE_ENV !== 'production')) {
      assert(typeof Promise !== 'undefined'`vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store`store must be called with the new operator.`)
    }
    
    const {
      plugins = [],// 插件
      strict = false,// 是否使用严格模式
      devtools
    } = options// 用户在createStore时传入的对象
    
    // store internal state    翻译:store内部状态
    this._committing = false // boolean 是否通过mutation修改数据,在启用严格模式时有用
    this._actions = Object.create(null) // {[type:string]: Array<Function>} 这里存储了所有的经过处理的action
    this._actionSubscribers = [] // Array<Function> 插件订阅actions时使用,会在dispatch函数部分讲
    this._mutations = Object.create(null) // {[type:string]: Array<Function>} 这里存储了所有的经过处理的mutations
    this._wrappedGetters = Object.create(null) // {[type:string]: Function} 这里储存了所有经过处理的getters
    this._modules = new ModuleCollection(options) // 这里将会把你传入的数据转化为树结构储存,具体在ModuleCollection处讲
    this._modulesNamespaceMap = Object.create(null) // {[name:string]: Module } namespaced为true的模块
    // 都在这里,用于mapState,mapMutations,mapGetters和mapActions
    this._subscribers = [] // Array<Function> 插件订阅mutations时使用,会在commit函数部分讲
    this._makeLocalGettersCache = Object.create(null)// {[namespace:string]: Object}有命名空间的模块的getters
    this._devtools = devtools
    
    // 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绑定在了store上,方便我们使用解构赋值
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)// 这里this绑定在了store上,方便我们使用解构赋值
    }

    // strict mode
    this.strict = strict // 是否使用严格模式,这里使用了deep watch,不建议生产环境使用,消耗性能

    const state = this._modules.root.state // 用户在createStore时传入的对象的state函数返回的对象

    // init root module.
    // this also recursively registers all sub-modules 
    // and collects all module getters inside this._wrappedGetters 
    // 翻译:初始化根模块,它同时递归注册所有子模块,且将所有模块的getters收集到this._wrappedGetters中
    installModule(this, state, [], this._modules.root)

    // initialize the store state, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    // 翻译:初始化store state,负责响应式,(且将_wrappedGetters注册为计算属性)
    // 这里说是计算属性,但当前版本(4.0.2)实际上并不是,具体看resetStoreState的部分
    resetStoreState(this, state)

    // apply plugins 翻译:应用插件
    plugins.forEach(plugin => plugin(this))
  }
// ...
}

首先可以看到,我们初始化store时其实是new了一个Store对象,这个Store对象提供了我们需要的所有功能。在我们new Store()时,如果在开发环境,首先会判断当前环境有无Promise,Promise在dispatch时还有初始化actions时会被使用。然后判断构造函数是否通过new调用,不是就报错,如下是assert的源码。

// src/util.js 64-66
export function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}

接着初始化了store内部状态,this._modules = new ModuleCollection(options)这句,就是让this._modules.root指向根模块,然后递归转化子模块。每个模块的_children属性的值是个对象,里边存放着其子模块,从而形成了类似DOM的树结构。我们可以通过this._modules.root._children.a._children.b这样获取根模块的子模块a的子模块b,但是光有这个树结构还不行,它只是负责储存信息,真正起作用的是store,所以通过installModule把模块中的mutations,actions,getters收集起来,然后通过resetStoreState初始化state和getters,这样我们就完成了初始化。

new ModuleCollection()

// src/module/module-collection.js 1-8 28-48
import Module from './module'
import { assert, forEachValue } from '../util'

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    // 翻译: 注册根模块(rawRootModule就是你传给createStore的对象)
    this.register([], rawRootModule, false)
  }
  
  register (path/* 路径 */, rawModule/* 用户传入的module */, runtime = true/* 是否运行时 */) {
  // 这里的runtime为true说明是你使用registerModule动态添加的,false为初始化时添加的,
  // runtime仅在unregisterModule时有用
    if (__DEV__) {// 这里会被编译为↓这段代码,当前环境为开发环境时,就会执行代码块中的代码
//if ((process.env.NODE_ENV !== 'production')) {
      assertRawModule(path, rawModule)// 如果actions不为函数,或有handler方法的对象,
      // 又或者getters或mutations不为函数时,就会报错
    }
    
    const newModule = new Module(rawModule, runtime)// 将当前module对象转化为Module实例
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }
    
    // register nested modules
    // 翻译: 注册嵌套模块(也就是递归注册)
    if (rawModule.modules) {
      forEachValue(rawModule.modules(rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

ModuleCollection就是用来生成module树的class,仅在new Store()时使用,童鞋们可以结合下图来看,assertRawModule我们等下再说,我们可以看到我们传入的参数变成了Module的实例,而根模块会被挂载在this.root上,也就是store._modules.root,然后通过递归调用register函数将子模块挂载到父节点上,从而形成如下图的一棵树(这树长啥样完全取决于你的输入)。

modules.png

assertRawModule

assertRawModule首先是遍历了assertTypes,对用户传入模块的mutations,actions,getters进行类型校验,之所以使用这种写法,是因为这里使用了策略模式(一种设计模式,有兴趣的童鞋可以看看《JavaScript设计模式与开发实践》),使得代码的可读性和可扩展性都得到了提升,如果未来要加入什么新的属性,再给assertTypes新加一个属性即可。

// src/module/module-collection.js 112-152
const functionAssert = {
  assertvalue => typeof value === 'function',// 类型校验函数
  expected'function'// 报错信息中的期望传入值,在后边会拼接到报错信息中
}

const objectAssert = {
  assertvalue => typeof value === 'function' ||
    (typeof value === 'object' && typeof value.handler === 'function'),
  expected'function or object with "handler" function'
}

const assertTypes = {
  getters: functionAssert,
  mutations: functionAssert,
  actions: objectAssert
}

// 用户写的mutations,actions,getters不对就报错的校验函数
function assertRawModule (path, rawModule) {
  Object.keys(assertTypes).forEach(key => {
    if (!rawModule[key]) return
    
    const assertOptions = assertTypes[key]// 这里是mutations,actions,getters之一
    // forEachValue用于遍历对象属性,源码下边有写
    forEachValue(rawModule[key], (value/* 对象的value */, type/* 对象的key */) => {
      assert(
        assertOptions.assert(value),// 调用类型判断函数,false就报错
        makeAssertionMessage(path, key, type, value, assertOptions.expected)
      )
    })
  })
}

// 这个函数就是用来拼接报错信息的
function makeAssertionMessage (path, key, type, value, expected) {
// mutations(或actions,getters)应该是啥啥类型,但是mutations.add(或者别的啥方法)
  let buf = `${key} should be ${expected} but "${key}.${type}"`
  if (path.length > 0) {
  // 在xx模块
    buf += ` in module "${path.join('.')}"`
  }
  // 是这个样子的
  buf += ` is ${JSON.stringify(value)}.`
  return buf
}

// src/util.js 52-54
// 用于遍历对象enumerable属性(即可以被for...in遍历到的属性)的辅助方法
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}

Module

上文说道,new ModuleCollection()返回一棵树,而new Module()就是这个树上的每个节点,它只负责保存用户输入的module对象,其次就是维护module之间的关系和提供一些便于操作module的接口了。

import { forEachValue } from '../util'

// Base data struct for store's module, package with some attribute and method
// 翻译: store模块的基础数据结构,封装了一些属性和方法
export default class Module {
// rawModule就是你写的module对象,runtime表示是否动态新增的
  constructor (rawModule, runtime) {
    this.runtime = runtime
    // Store some children item
    // 翻译:保存一些子模块
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    // 翻译:保存用户传入的原始module对象
    this._rawModule = rawModule
    const rawState = rawModule.state

    // Store the origin module's state
    // 保存原module对象的state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }

  get namespaced () {// 这里只写了get方法,表示这是一个只读属性
    return !!this._rawModule.namespaced// !!等价于利用Boolean()转换为布尔值
  }
  // 以下都是对_children对象的增删改查
  addChild (key, module) {
    this._children[key] = module
  }

  removeChild (key) {
    delete this._children[key]
  }

  getChild (key) {
    return this._children[key]
  }

  hasChild (key) {
    return key in this._children
  }
// 用于热重载hotUpdate
  update (rawModule) {
  // 因为读取module.namespaced属性时转换了布尔值,所以undefined相当于false
    this._rawModule.namespaced = rawModule.namespaced
    if (rawModule.actions) {
      this._rawModule.actions = rawModule.actions
    }
    if (rawModule.mutations) {
      this._rawModule.mutations = rawModule.mutations
    }
    if (rawModule.getters) {
      this._rawModule.getters = rawModule.getters
    }
  }

  // 下边都是遍历内部属性的方法
  // 遍历子节点
  forEachChild (fn) {
  // forEachValue用于遍历对象属性,源码下边有写
    forEachValue(this._children, fn)
  }

  // 遍历getters
  forEachGetter (fn) {
    if (this._rawModule.getters) {
      forEachValue(this._rawModule.getters, fn)
    }
  }
  
  // 遍历actions
  forEachAction (fn) {
    if (this._rawModule.actions) {
      forEachValue(this._rawModule.actions, fn)
    }
  }

  // 遍历mutations
  forEachMutation (fn) {
    if (this._rawModule.mutations) {
      forEachValue(this._rawModule.mutations, fn)
    }
  }
}

// src/util.js 52-54
// 用于遍历对象enumerable属性(即可以被for...in遍历到的属性)的辅助方法
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}

我们可以看到,其实addChild,removeChild,getChild和hasChild都只是对_children对象的成员做增删查,一方面暴露了_children这个内部属性的接口,一方面提升了可读性。update这个方法用于hotUpdate,在使用webpack等工具时,可以开启热更新,用于提升开发效率,配置了mutations的热重载,你添加新的mutations方法的时候就不会刷新页面,而是加载一段新的js,不配页面就会刷新。

ModuleCollection

看完module的源码,我们已经清楚了module是用于保存用户传入module对象的一个基础数据结构,而它们所组成的树就是ModuleCollection,它负责基于路径遍历路径上的模块,并对其进行处理。下面我们来看看它剩下部分的代码,也就是依据路径完成获取模块,获取命名空间,热更新,卸载动态模块和查看模块是否注册:

// src/module-collection.js 9-27 49-111
export default class ModuleCollection {
  //...
  // 根据路径获取相应模块,比如a是root的子模块,b是a的子模块,则可以使用['a','b']获取b模块
  // 等价于root._children.a._children.b
  get (path) {
    return path.reduce((module/* 上一个项计算得到的module */, key/* path当前项,string类型 */) => {
      return module.getChild(key)/* 返回子模块,等价于module._children[key] */
    }, this.root/* 初始值root模块 */)
  }

  // 根据路径拼接命名空间字符串,如果a,b模块的namespaced都为true,则会拼接出'a/b/'
  getNamespace (path/* Array<string> */) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }
  
  // 用于热更新hotUpdate,从根模块递归向下更新整个module树,注意不能在此时添加新模块
  update (rawRootModule) {
    update([], this.root, rawRootModule)
  }
  
  // 用于unregisterModule,动态卸载一个模块,这个模块只能卸载registerModule注册的模块,
  // 也就是只能卸载动态添加的模块
  unregister (path/* Array<string> */) {
    // 假设path为[a,b],path.slice(0, -1)会返回去掉最后一项的副本,也就是[a],
    // 所以parent会是父模块
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]// 这里会得到最后一项,也就是'b'
    const child = parent.getChild(key)// 获取子模块
    
    if (!child) {// 没有子模块就报个错,然后返回
      if (__DEV__) {// 开发环境调用
        console.warn(
          `[vuex] trying to unregister module '${key}', which is ` +
          `not registered`
          // 翻译:[vuex]尝试卸载模块b,然鹅这个模块还没注册
        )
      }
      return
    }
    // 子模块不是通过registerModule添加的,直接返回
    if (!child.runtime) {
      return
    }
    // 程序能跑到这里,说明存在子模块,且子模块是通过registerModule添加的
    parent.removeChild(key)
  }

  // 是否已注册模块,通过路径查找改模块是否在树上
  isRegistered (path/* Array<string> */) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    
    if (parent) {
      return parent.hasChild(key)
    }
    
    return false
  }
}

// 更新module树,用于热更新hotUpdate
function update (path, targetModule, newModule) {
  if (__DEV__) {// 开发环境验证用户输入
    assertRawModule(path, newModule)// 上文讲过,不再赘述
  }

  // update target module
  // 翻译:更新目标模块
  targetModule.update(newModule)

  // update nested modules
  // 翻译:更新嵌套模块
  if (newModule.modules) {
    for (const key in newModule.modules) {
      // 不支持在热更新时添加新模块
      if (!targetModule.getChild(key)) {
        if (__DEV__) {
          console.warn(
            `[vuex] trying to add a new module '${key}' on hot reloading, ` +
            'manual reload is needed'
            // 翻译:[vuex]尝试在热更新时添加新模块xxx,
            // 建议你手动重载
          )
        }
        return
      }
      // 递归更新已存在的子模块
      update(
        path.concat(key),// 延长path,[a,b] => [a,b,c]
        targetModule.getChild(key),// 旧的目标模块
        newModule.modules[key]// 新的模块
      )
    }
  }
}

installModule

好了,我们通过ModuleCollection形成了一棵用于保存用户传入对象的树,但是这棵树还不能直接使用,我们需要通过installModule把模块中的mutations,actions,getters收集起来,绑到store上,这样我们才能为dispatch和commit收集需要执行的函数,以及为下一步的resetStoreState做好准备。只要更新了模块,就会调用installModule,只要调用了installModule,就会调用resetStoreState,换言之,只要你输入的对象发生改变,Store的内部状态就会更新。

// src/store-util.js 72-122
// installModule安装模块,只要模块发生变动后都会调用,比如registerModule,
// unregisterModule hotUpdate和new Store()
export function installModule (store, rootState, path, module, hot) {
// store就是Store实例,rootState是根模块的state,path路径,module模块
// hot在初始化时为假值,registerModule时,我们可以通过options参数的preserveState
// 来控制hot的值,其余时候为true
  const isRoot = !path.length// path为[]时,表示这是根模块
  // 调用ModuleCollection的获取命名空间,前文已讲,不再赘述
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  // 将模块注册到store._modulesNamespaceMap对象上,用于mapState,mapMutations,mapGetters和mapActions
  if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
    // 翻译:[vuex] xxx命名空间重复了
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  // 设置state
  if (!isRoot && !hot) {
    // 通过store.state.a.b.c的方式获取到相应父模块的state
    const parentState = getNestedState(rootState, path.slice(0, -1))
    // 模块名
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      if (__DEV__) {// 开发环境执行
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
            // 翻译:[vuex]父模块的state的xxx属性会被重名的模块覆盖
          )
        }
      }
      parentState[moduleName] = module.state
    })
  }
  
  // 这个local就是我们写actions时那个'与 store 实例具有相同方法和属性的 context 对象'了
  // module.context用于mapState,mapMutations,mapGetters和mapActions
  const local = module.context = makeLocalContext(store, namespace, path)

  module.forEachMutation((mutation, key) => {// 遍历模块的每个mutation
    const namespacedType = namespace + key
    // 将mutation处理后放进store._mutations[namespacedType]这个数组中,等待commit时调用
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {// 遍历模块的每个action
    const type = action.root ? key : namespace + key
    // 在assertRawModule这节时已经说过action可以是函数也可以是有handler的对象
    const handler = action.handler || action
    // 将action处理后放进store._actions[namespacedType]这个数组中,等待dispatch时调用
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) => {// 遍历模块的每个getter
    const namespacedType = namespace + key
    // 将getter处理后给state._wrappedGetters[namespacedType]赋值,等待resetStoreState时使用
    registerGetter(store, namespacedType, getter, local)
  })

  module.forEachChild((child, key) => {// 遍历模块的每个child
  // 递归调用installModule,从而完成对整个树的遍历
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

// src/store-util.js 262-264
// 通过store.state.a.b.c.d的方式获取到相应模块的state
export function getNestedState (state, path) {
  return path.reduce((state, key) => state[key], state)
}
// src/store.js 259-264
// 用于在修改state时调用,以保证在严格模式不会报错
  _withCommit (fn) {
    // 因为_withCommit是Store原型上的方法,所以this指向store实例
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
 // src/store-util.js 254-260
 // 启用严格模式时,会深度监听store.state,一旦发生变动就检查store._committing的值,为false就报错
 // 所以任何对store的改动vuex都会调用Store.prototype._withCommit()来修改
 function enableStrictMode (store) {
  watch(() => store._state.data() => {
    if (__DEV__) {
      assert(store._committing`do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deeptrueflush'sync' })
}

installModule.png 结合上图我们看一下这段代码,首先是将带有命名空间的模块存到store._modulesNamespaceMap中,这将在我们使用mapStatemapMutationsmapGettersmapActions时,通过namespace直接获取模块。
然后执行parentState[moduleName] = module.state,也就是把根模块以外的模块的state,添加到父模块的state上,这就是文档中我们可以通过store.state.a拿到moduleA 的状态的原因,文档截图如下:

moduleVuexDoc.png makeLocalContext这个函数我们等下再谈,可以认为localmodule.context等价于我们写actions时那个'与 store 实例具有相同方法和属性的 context 对象',这里的module.context,同样在我们使用 mapGetters这类辅助函数时有用,上文提到,我们可以通过store._modulesNamespaceMap直接获取module,而local更是可以通过刚刚取得的module.context获得,这也是module.context的作用。

下面的代码对模块的mutationsactionsgetters进行循环,本质上是利用柯里化(一种通过高阶函数返回新函数的技术,利用闭包可以实现将原函数绑定this和事先传入部分参数的技术)减少函数参数数量和绑定this后,将其收集到store_mutations_actions_wrappedGetters这3个内部属性中。代码解析见后文registerMutationregisterActionregisterGetter

最后进行了递归,让子模块也执行installModule(),所以综合来看,installModule()做的事情,就是递归地将模块中的内容,收集到store的内部属性中,成为装填的弹药。

makeLocalContext

// src/store-util.js 123-179
/**
 * make localized dispatch, commit, getters and state
 * if there is no namespace, just use root ones
 */
 /**
  * 翻译:创建局部的dispatch,commit,getters和state
  * 如果么得命名空间,就用根元素上的那个就好了
  */
function makeLocalContext (store/* store实例 */, namespace/* 命名空间 */, path/* 模块路径 */) {
  const noNamespace = namespace === ''// 无命名空间

  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      // unifyObjectStyle方法在dispatch等方法中很常用,就是为了兼容把type放到payload中的写法,
      // 统一输入,方便后边操作,代码贴下面了
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args
      
      // 这段代码可以看文档→  https://next.vuex.vuejs.org/zh/api/#dispatch
      // 中的这句话'`options` 里可以有 `root: true`,它允许在命名空间模块里分发根的 action'
      // 也就是说options.root为true的话,type就不会加上命名空间前缀(如'a/b/'),从而dispatch
      // 根模块的actions
      if (!options || !options.root) {
        type = namespace + type
        if (__DEV__ && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          // 翻译:[vuex]未知的局部action类型:a/b/xxx,全局(根模块)的action类型是xxx
          return
        }
      }
 
      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args
      
      // 同样参考文档→ https://next.vuex.vuejs.org/zh/api/#commit
      // options 里可以有 root: true,它允许在命名空间模块里提交根的 mutation。
      if (!options || !options.root) {
        type = namespace + type
        if (__DEV__ && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          // 翻译:[vuex]未知的局部mutation类型a/b/xxx,全局类型:xxx
          return
        }
      }
      
      store.commit(type, payload, options)
    }
  }
  // getters and state object must be gotten lazily
  // because they will be changed by state update
  // 翻译:getters和state对象必须懒获取
  // 因为它们会被state更新修改
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        // makeLocalGetters()返回一个对象,使用局部的getter名就可以获取到相应getter函数
        : () => makeLocalGetters(store, namespace)
    },
    state: {
    // getNestedState,通过a.b.c.d的方式获取该模块state 前文已讲,不再赘述
      get() => getNestedState(store.state, path)
    }
  })

  return local
}

// src/store-util.js 266-279
// 统一传入参数类型
export function unifyObjectStyle (type, payload, options) {
  if (isObject(type) && type.type) {// 也就是没传type,直接传payload,且payload里有type
  // 这时第一个参数是payload,第二个参数是options,所以下边就是把传参后移一位
    options = payload
    payload = type
    type = type.type
  }

  if (__DEV__) {
    assert(typeof type === 'string'`expects string as the type, but found ${typeof type}.`)
    // 翻译:期望type是string类型,但是你给的是xxx类型
  }

  return { type, payload, options }
}

// src/util.js 56-58
export function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}

// src/store-util.js 180-203
// 创建局部getters
export function makeLocalGetters (store, namespace) {
// 没有缓存就创建,有缓存就直接返回,防止二次计算
// 能调用这个函数的getter都是有命名空间的,比如'a/b/xxxGetters'
  if (!store._makeLocalGettersCache[namespace]) {
    const gettersProxy = {}
    const splitPos = namespace.length
    // 可以理解为所有的getters都以namespace+type的形式的key存放在了store.getters中
    // 即读取store.getters['a/b/xxxGetters']时,可以获得该getter的运算结果
    Object.keys(store.getters).forEach(type => {
      // skip if the target getter is not match this namespace
      // 翻译:如果目标getter的命名空间不匹配的话就直接跳过
      // 假设type是'a/b/xxxGetters',slice后就是'a/b/',如果跟传入的命名空间不一致就返回
      if (type.slice(0, splitPos) !== namespace) return

      // extract local getter type
      // 翻译:提取局部getter类型
      const localType = type.slice(splitPos)// 也就是'xxxGetters'

      // Add a port to the getters proxy.
      // Define as getter property because
      // we do not want to evaluate the getters in this time.
      // 翻译:给gettersProxy添加一个端口
      // 定义为一个getter属性因为我们并不想在此时求值
      Object.defineProperty(gettersProxy, localType, {
        get() => store.getters[type],
        enumerabletrue
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }

  return store._makeLocalGettersCache[namespace]
}

从上述代码中可以看出,makeLocalContext()返回的对象其实就是帮我们用局部的key拼接上namespace获取全局的mutations,actions和getters,或者帮我们通过a.b.c.d的方法获取到局部state。

接下来我们看下之前说的把模块上的mutations,actions和getters先通过柯里化绑定this和预设参数,然后再收集的代码:

registerMutation

// store:Store实例, type:命名空间加key, handler:mutation方法, local:上文提到的局部对象
function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  // 这段代码等价于:
  // if(!store._mutations[type]){
  //   store._mutations[type] = [];
  // }
  // const entry = store._mutations[type]
  entry.push(function wrappedMutationHandler (payload) {
    // 使用了柯里化之后,我们只用传入payload参数,且this也被绑定了
    handler.call(store, local.state, payload)
  })
}

registerAction

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {
    // 跟mutation不一样的地方在于没有直接返回结果,而是对结果进行处理后再返回
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    // 这里之所以要保证是个promise就是因为在dispatch时会用Promise.all
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    // 这块是给vue-devtools报个错,顺便抛出错误,如果reject的话
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

// src/util.js 60-62
// 这样实现估计是为了兼容性,因为有的Promise是利用polyfill实现的
export function isPromise (val) {
  return val && typeof val.then === 'function'
}

registerGetter

function registerGetter (store, type, rawGetter, local) {
  // 与mutation和action不同的在于getter是个函数,而非函数数组
  if (store._wrappedGetters[type]) {
    if (__DEV__) {
      console.error(`[vuex] duplicate getter key: ${type}`)
      // 翻译:[vuex]重复的getter key: xxx
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state// local state 局部state
      local.getters// local getters 局部getters
      store.state// root state 根模块state
      store.getters // root getters 跟模块getters
    )
  }
}

resetStoreState

在完成installModule()后,我们实际上已经把模块树上所有模块的内容都绑定到了store上,但是我们清楚,在使用getter获取值时,我们需要的是值,而不是一个函数,这就意味着我们需要让我们在获取这个值时调用刚刚收集到的getter函数,从而获取这个值(当前版本不是计算属性,因为缓存会导致bug,后续版本会修复)。而state目前也不是响应式的,所以我们需要调用resetStoreState(),完成初始化的最后一步。

export function resetStoreState (store, state, hot) {
// store:store实例, state:就是store.state, hot是否hotUpdate
  const oldState = store._state

  // bind store public getters
  // 翻译:绑定store对外暴露的getters
  store.getters = {}
  // reset local getters cache
  // 翻译:重置局部getters缓存
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computedObj = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // 翻译:使用computed,从而利用其缓存机制(注意,这是vuex3的注释,这里没有删掉)
    // direct inline function use will lead to closure preserving oldState.
    // using partial to return function with only arguments preserved in closure environment.
    // 翻译:直接写fn(store)的话,闭包会把oldState变量也缓存起来
    // 使用partial函数返回的函数只会把参数保存在其闭包中(partial函数实现照例贴在下面)
    computedObj[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      // TODO: use `computed` when it's possible. at the moment we can't due to
      // 翻译:TODO: 等computed能使用了再用,现在我们用不了,因为这个bug↓
      // https://github.com/vuejs/vuex/pull/1883
      get() => computedObj[key](),// 这就是每次获取属性时都调用getter函数的精髓所在
      enumerabletrue // for local getters
    })
  })
  // 我们访问store.state实际上是在访问store._state.data
  // 这也是store.state实现响应式的关键
  store._state = reactive({
    data: state
  })

  // enable strict mode for new state
  // 如果你设置了严格模式,这里会开始深度监听state,前文已讲,不再赘述
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldState) {
    if (hot) {// 是否使用hotUpdate
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      // 翻译:在所有订阅的观察者中分发改动,
      // 强制getter重新求值,用于热重载
      store._withCommit(() => {
        oldState.data = null
      })
    }
  }
}

// src/util.js 68-73
// 返回来的函数会通过闭包获取fn和arg
// 因为在这个作用域中只有这两个变量,所以不会缓存多余的变量,可以提升性能
export function partial (fn, arg) {
  return function () {
    return fn(arg)
  }
}

我们可以看到store.getters通过Object.definePropertystore._wrappedGetters上的所有key都绑定到了自己身上,而且因为我们访问store.getters上的key,实际上会调用computedObj[key]()也就是fn(store)(这里的fn就是store._wrappedGetters上的value),这就是我们使用getters时可以直接拿到计算后的值的原因。
store.state实现了响应式就是因为我们访问store.state实际上访问的是store._state.data,而这里使用了vue3的reactive()函数实现了响应式。

install

接着我们说下Store上的其他属性方法,在我们完成了createStore()之后,下一步就是app.use(store),这一步本质上是调用Store.prototype.install()方法,结合useStore()方法,我们可以看出这本质上就是Provide/Inject组合,从而实现我们在哪都可以调用useStore获得store.

// src/store.js 72-83
export class Store {
  // ...
  install (app, injectKey) {
    // app就是createApp返回的那个app, injectKey的类型是InjectionKey<Store<any>> | string
    // Vue 提供了一个 `InjectionKey` 接口,该接口是扩展了 `Symbol` 的泛型类型。
    // 它可用于在生产者和消费者之间同步 inject 值的类型
    // https://v3.cn.vuejs.org/api/composition-api.html#provide-inject
    app.provide(injectKey || storeKey, this)// 这里的storeKey就是'store',这句是为了实现useStore
    app.config.globalProperties.$store = this// 这句是为了支持this.$store
    // 这一块是初始化devtool的代码
    const useDevtools = this._devtools !== undefined
      ? this._devtools
      : __DEV__ || __VUE_PROD_DEVTOOLS__

    if (useDevtools) {
      addDevtools(app, this)
    }
  }
}

// src/injectKey.js
import { inject } from 'vue'

export const storeKey = 'store'

export function useStore (key = null) {
  return inject(key !== null ? key : storeKey)
}

state

使用vue3的reactive实现了响应式。

// src/store.js 85-93
export class Store {
  // ...
  get state () {
    // 这里的this._state是reactive(this._modules.root.state)返回的结果
    // 这个state上已经挂载了整个模块树的state
    return this._state.data
  }

  set state (v) {
    if (__DEV__) {
      assert(false`use store.replaceState() to explicit replace store state.`)
      // 翻译:使用store.replaceState()来意图清晰地替换store.state
    }
  }
}

commit & dispatch

接着我们来看commit()dispatch(),本质上都只是把installModule()时收集到的函数遍历一遍而已,看起来和自定义事件很像,但是自定义事件中,我们只能知道事件发生了,又有谁订阅,但是这个事件会修改几个状态,又有几个事件可以修改某个状态,这就完全抓瞎了,这就是为什么全局事件总线(GlobalEventBus)被废弃掉的原因。

// commit
// src/store.js 95-130
export class Store {
  // ...
  commit (_type, _payload, _options) {
    // check object-style commit
    // 翻译:检查对象类型的commit
    // 就是把type写到payload里的写法
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)// 前文已讲,不再赘述

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown mutation type: ${type}`)
        // 翻译:[vuex]未知的mutation类型:xxx
      }
      return
    }
    this._withCommit(() => {
      // 遍历store._mutations[type]数组,给每个函数传参
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // 调用插件
    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      // 翻译:如果订阅者同步调用unsubscribe,浅拷贝可防止迭代器失效
      .forEach(sub => sub(mutation, this.state))
      
    // 这段就是说已经不支持options.silent了
    if (
      __DEV__ &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
        // 翻译: [vuex]mutation类型:xxx。Slient选项已经被废弃。
        // 请使用vue-devtools的filter functionality
      )
    }
  }
}
// dispatch
// src/store.js 132-191
export class Store {
  // ...
  dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      if (__DEV__) {
        console.error(`[vuex] unknown action type: ${type}`)
        // 翻译:[vuex]未知的action类型:xxx
      }
      return
    }
    
    // 调用插件的前置订阅
    try {
      this._actionSubscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        // 翻译:如果订阅者同步调用unsubscribe,浅拷贝可防止迭代器失效
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (__DEV__) {
        console.warn(`[vuex] error in before action subscribers: `)
        // 翻译:[vuex]前置action订阅者出错了
        console.error(e)
      }
    }
    // Promise版本的遍历函数数组
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) => {
      result.then(res => {
      // 调用插件的后置订阅
        try {
          this._actionSubscribers
            .filter(sub => sub.after)
            .forEach(sub => sub.after(action, this.state))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in after action subscribers: `)
            // 翻译:[vuex]后置action订阅出错了
            console.error(e)
          }
        }
        resolve(res)
      }, error => {
      // 调用插件的error订阅
        try {
          this._actionSubscribers
            .filter(sub => sub.error)
            .forEach(sub => sub.error(action, this.state, error))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in error action subscribers: `)
             // 翻译:[vuex]action的error订阅出错了
            console.error(e)
          }
        }
        reject(error)
      })
    })
  }

registerModule

先调用ModuleCollection的register()方法,通过递归将模块绑定到模块树上,而模块树一发生变动,就要通过installModule()resetStoreState()这两个方法将模块树的内容同步到store实例上。

// src/store.js 215-227
export class Store {
  // ...
  registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      // 翻译:模块的路径必须是字符串或数组
      assert(path.length > 0'cannot register the root module by using registerModule.')
      // 翻译:不能用registerModule注册根模块
    }
    // 调用ModuleCollection的register方法,通过递归将模块绑定到模块树上
    this._modules.register(path, rawModule)
    // 将模块上的数据绑定到store上
    installModule(thisthis.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    // 重置store以更新getters...
    resetStoreState(thisthis.state)
  }
}

unregisterModule

先调用ModuleCollection的unregister()方法,卸载registerModule()注册的模块,此时store上绑定的内部属性_actions等,都是卸载前的内容,所以要通过resetStore()将其全部清空,然后重新通过installModule()收集,再通过resetStoreState()重置getters和state。

// src/store.js 229-242
export class Store {
  // ...
  unregisterModule (path) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      // 翻译:模块的路径必须是字符串或数组
    }
    // 调用ModuleCollection的unregister方法,卸载registerModule注册的模块
    this._modules.unregister(path)
    this._withCommit(() => {
      // 找到父模块的state
      const parentState = getNestedState(this.state, path.slice(0, -1))
      // 删除子模块的state
      delete parentState[path[path.length - 1]]
    })
    resetStore(this)
  }
}

// src/store-util.js 18-28
export function resetStore (store, hot) {
  // 重置部分内部属性,从模块树上重新获取
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.roottrue)
  // reset state
  resetStoreState(store, state, hot)
}

hasModule

调用ModuleCollection的isRegistered方法,获取其父模块,然后检查父模块的_children属性上有无该子模块。

// src/store.js 244-252
export class Store {
  // ...
  hasModule (path) {
    if (typeof path === 'string') path = [path]

    if (__DEV__) {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      // 翻译:模块的路径必须是字符串或数组
    }

    return this._modules.isRegistered(path)
  }
}

watch

调用vue3的watch

// src/store.js 202-207
export class Store {
  // ...
  watch (getter, cb, options) {
    if (__DEV__) {
      assert(typeof getter === 'function', `store.watch only accepts a function.`)
      // 翻译:store.watch只接受函数
    }
    return watch(() => getter(this.state, this.getters), cb, Object.assign({}, options))
  }
}

subscribe & subscribeAction

具体用法见官网

// src/store.js 193-200
export class Store {
  // ...
  subscribe (fn, options) {
    // 把fn添加到this._subscribers数组中,供commit时使用,返回取消订阅函数
    return genericSubscribe(fn, this._subscribers, options)
  }

  subscribeAction (fn, options) {
    // { before: fn }表示fn会在action执行前调用
    const subs = typeof fn === 'function' ? { before: fn } : fn
    // 把subs添加到this._actionSubscribers数组中,供dispatch时使用
    return genericSubscribe(subs, this._actionSubscribers, options)
  }
}

// src/store-util.js 4-16
// 通用的订阅
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)
    }
  }
}

hotUpdate

用于热更新,递归更新模块树,然后重置Store。

// src/store.js 254-257
export class Store {
  // ...
  hotUpdate (newOptions) {
    // 调用ModuleCollection上的update方法更新模块树
    this._modules.update(newOptions)
    resetStore(thistrue)
  }
}

replaceState

替换state

// src/store.js 209-213
export class Store {
  // ...
  replaceState (state) {
    this._withCommit(() => {
      this._state.data = state
    })
  }

helpers

在看mapState,mapMutations,mapGetters和mapActions的源码之前,先看下这四个函数都通用的代码。

// src/helpers.js 138-194
/**
 * Return a function expect two param contains namespace and map. it will normalize the namespace and then the param's function will handle the new namespace and the map.
 * 翻译:返回一个接收namespace和map这两个参数的函数,这个返回的函数会标准化namespace,
 * 而作为参数传入的函数(fn)会处理新namespace和map
 * @param {Function} fn
 * @return {Function}
 */
function normalizeNamespace (fn) {
  return (namespace, map) => {
    if (typeof namespace !== 'string') {// 第一个参数写的是map
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {// namespace最后不是/
      namespace += '/'
    }
    return fn(namespace, map)
  }
}

/**
 * Validate whether given map is valid or not
 * 翻译:校验map是否有效(校验是否数组或对象)
 * @param {*} map
 * @return {Boolean}
 */
function isValidMap (map) {
  return Array.isArray(map) || isObject(map)
}


/**
 * Normalize the map 标准化map(就是将string[]或{[key:string]:string|Function}转化为{key:string;value:string|Function}[])
 * normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
 * normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
 * @param {Array|Object} map
 * @return {Object}
 */
function normalizeMap (map) {
  if (!isValidMap(map)) {// 不是数组或对象
    return []
  }
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

/**
 * Search a special module from store by namespace. if module not exist, print error message.
 * 翻译:在store中通过命名空间查找一个特定的模块。如果模块不存在,就报错。
 * @param {Object} store
 * @param {String} helper
 * @param {String} namespace
 * @return {Object}
 */
function getModuleByNamespace (store, helper, namespace) {
  const module = store._modulesNamespaceMap[namespace]// 在installModule时缓存了
  if (__DEV__ && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
}

mapState

通过在installModule()时在store._modulesNamespaceMap对象中缓存的module的context,我们可以取得局部的state,其余三个方法的代码也大同小异。

// src/helpers.js 3-34
/**
 * Reduce the code which written in Vue.js for getting the state.
 * 翻译:减少在Vue中写的获取state的代码
 * @param {String} [namespace] - Module's namespace 模块的命名空间
 * @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it.
 * 翻译:Object的成员可以是一个接收state和getters作为参数的函数,你可以在这个函数中对state和getters做些事情
 * @param {Object}
 */
// normalizeNamespace用于将输入标准化
export const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  if (__DEV__ && !isValidMap(states)) {
  // 开发环境且states参数不是数组或对象则报错
    console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
    // 翻译:[vuex] mapState: mapper参数必须是数组或对象
  }
  // 用于将数组或对象统一成对象数组
  normalizeMap(states).forEach(({ key/* string */, val /* string|Function */}) => {
    res[key] = function mappedState () {
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        // 通过命名空间获取模块(installModule时进行了缓存)
        const module = getModuleByNamespace(this.$store'mapState', namespace)
        if (!module) {
          return
        }
        // module.context也在installModule时进行了缓存
        state = module.context.state
        getters = module.context.getters
      }
      return typeof val === 'function'
      // 一个接收state和getters作为参数的函数,你可以在这个函数中对state和getters做些事情
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

mapMutations

通过在installModule()时在store._modulesNamespaceMap对象中缓存的module的context,我们可以取得局部的commit,其余三个方法的代码也大同小异。

// src/helpers.js 36-64
/**
 * Reduce the code which written in Vue.js for committing the mutation
 * 翻译:减少在Vue中commit mutation的代码
 * @param {String} [namespace] - Module's namespace 模块的命名空间
 * @param {Object|Array} mutations # Object's item can be a function which accept `commit` function as the first param, it can accept another params. You can commit mutation and do any other things in this function. specially, You need to pass another params from the mapped function.
 * 翻译:对象的成员可以是一个接收commit函数作为第一个参数的函数,它还可以接收别的参数。
 * 在这个函数中你可以commit mutation以及干点其他的事。尤其是,需要从映射函数传递另一个参数时。
 * @return {Object}
 */
export const mapMutations = normalizeNamespace((namespace, mutations) => {
  const res = {}
  if (__DEV__ && !isValidMap(mutations)) {
    console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object')
    // 翻译:[vuex] mapMutations: mapper参数必须是数组或对象
  }
  normalizeMap(mutations).forEach(({ key, val }) => {
    res[key] = function mappedMutation (...args) {
      // Get the commit method from store
      // 从store获取commit方法
      let commit = this.$store.commit
      if (namespace) {
        const module = getModuleByNamespace(this.$store'mapMutations', namespace)
        if (!module) {
          return
        }
        commit = module.context.commit
      }
      return typeof val === 'function'
        ? val.apply(this, [commit].concat(args))
        : commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

mapGetters

通过在installModule()时在store._modulesNamespaceMap对象中缓存的module的context,我们可以取得局部的getters,其余三个方法的代码也大同小异。

// src/helpers.js 66-94
/**
 * Reduce the code which written in Vue.js for getting the getters
 * 翻译:减少获取getters的Vue代码
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Arraygetters
 * @return {Object}
 */
export const mapGetters = normalizeNamespace((namespace, getters) => {
  const res = {}
  if (__DEV__ && !isValidMap(getters)) {
    console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object')
    // 翻译:[vuex] mapGetters: mapper参数必须是数组或对象
  }
  normalizeMap(getters).forEach(({ key, val }) => {
    // The namespace has been mutated by normalizeNamespace
    // 翻译:命名空间已被normalizeNamespace修改
    val = namespace + val
    res[key] = function mappedGetter () {
      if (namespace && !getModuleByNamespace(this.$store'mapGetters', namespace)) {
        return
      }
      if (__DEV__ && !(val in this.$store.getters)) {
        console.error(`[vuex] unknown getter: ${val}`)
        // 翻译:[vuex]未知getter:xxx
        return
      }
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

mapActions

通过在installModule()时在store._modulesNamespaceMap对象中缓存的module的context,我们可以取得局部的dispatch,其余三个方法的代码也大同小异。

// src/helpers.js 96-124
/**
 * Reduce the code which written in Vue.js for dispatch the action
 * 减少dispatch action的Vue代码
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} actions # Object's item can be a function which accept `dispatch` function as the first param, it can accept anthor params. You can dispatch action and do any other things in this function. specially, You need to pass anthor params from the mapped function.
 * 翻译:对象的成员可以是接收dispatch函数作为第一个参数的函数,也可以有别的参数。在这个函数中,
 * 你可以dispatch action,也可以干点别的事。尤其是,需要从映射函数传递另一个参数时。
 * @return {Object}
 */
export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
  if (__DEV__ && !isValidMap(actions)) {
    console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object')
    // 翻译:[vuex] mapActions: mapper参数必须是数组或对象
  }
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction (...args) {
      // get dispatch function from store
      // 翻译:从store中获取dispatch函数
      let dispatch = this.$store.dispatch
      if (namespace) {
        const module = getModuleByNamespace(this.$store'mapActions', namespace)
        if (!module) {
          return
        }
        dispatch = module.context.dispatch
      }
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

createNamespacedHelpers

通过柯里化,预设namespace

// src/helpers.js 126-136
/**
 * Rebinding namespace param for mapXXX function in special scoped, and return them by simple object
 * 翻译:在特殊范围内为 mapXXX 函数重新绑定命名空间参数,并通过简单对象返回它们
 * @param {Stringnamespace
 * @return {Object}
 */
export const createNamespacedHelpers = (namespace) => ({
  mapState: mapState.bind(null, namespace),
  mapGetters: mapGetters.bind(null, namespace),
  mapMutations: mapMutations.bind(null, namespace),
  mapActions: mapActions.bind(null, namespace)
})

深拷贝deepCopy

考虑到循环结构,深度复制给定对象,该函数缓存所有嵌套对象及其副本,如果检测到循环结构,则使用缓存副本以避免无限循环。这是一个值得背下来的函数。

// src/util.js 1-47
/**
 * Get the first item that pass the test
 * by second argument function
 *
 * @param {Arraylist
 * @param {Functionf
 * @return {*}
 */
export function find (list, f) {
  return list.filter(f)[0] // 就是Array.prototype.find的polyfill
}

/**
 * Deep copy the given object considering circular structure.
 * This function caches all nested objects and its copies.
 * If it detects circular structure, use cached copy to avoid infinite loop.
 * 翻译:考虑到循环结构,深度复制给定对象
 * 该函数缓存所有嵌套对象及其副本
 * 如果检测到循环结构,则使用缓存副本以避免无限循环。
 *
 * @param {*obj
 * @param {Array<Object>cache
 * @return {*}
 */
export function deepCopy (obj, cache = []) {
  // just return if obj is immutable value
  // 翻译:如果obj是不可变值就直接返回
  // 说人话就是原始类型和函数
  if (obj === null || typeof obj !== 'object') {
    return obj
  }

  // if obj is hit, it is in circular structure
  // 翻译:如果命中了,它就是循环结构
  const hit = find(cache, c => c.original === obj)
  if (hit) {
    return hit.copy
  }

  const copy = Array.isArray(obj) ? [] : {}
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  // 翻译:先把副本保存到缓存中
  // 因为我们想在递归 deepCopy 中引用它
  cache.push({
    original: obj,
    copy
  })

  Object.keys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}

plugins

devtool

logger

以下是默认折叠和默认展开的打印信息: groupCollapsed.png

group2.png

// src/plugins/logger.js 
// Credits: borrowed code from fcomb/redux-logger

import { deepCopy } from '../util'

export function createLogger ({
  collapsed = true,// 打印信息是否折叠
  filter = (mutation, stateBefore, stateAfter) => true,// mutation过滤器,不打印的返回false即可
  transformer = state => state,// state信息的格式化函数
  mutationTransformer = mut => mut, // mutation信息的格式化函数
  actionFilter = (action, state) => true,// action过滤器,不打印的返回false即可
  actionTransformer = act => act,// action信息的格式化函数
  logMutations = true,// 是否打印mutations变化
  logActions = true,// 是否打印actions变化
  logger = console
} = {}) {
  return store => {
    // 对store.state进行深拷贝,用于mutation执行后做对比
    let prevState = deepCopy(store.state)

    if (typeof logger === 'undefined') {
      return
    }

    if (logMutations) {// 是否打印mutations
      // 订阅函数subscribe见subscribe & subscribeAction
      store.subscribe((mutation, state) => {
        // 这个函数执行时某个mutation刚刚执行完
        const nextState = deepCopy(state)// 深拷贝当前state

        if (filter(mutation, prevState, nextState)) {// 过滤mutation
          const formattedTime = getFormattedTime()// 格式化时间,代码贴在下面了
          const formattedMutation = mutationTransformer(mutation)
          const message = `mutation ${mutation.type}${formattedTime}`

          startMessage(logger, message, collapsed)// 开始打印,代码贴在下面了,打印效果图上边有
          // 打印旧state   %c后的文字(prev state)可修改样式
          logger.log('%c prev state''color: #9E9E9E; font-weight: bold'transformer(prevState))
          // 打印mutation  第二个参数是样式
          logger.log('%c mutation''color: #03A9F4; font-weight: bold', formattedMutation)
          // 打印新state   
          logger.log('%c next state''color: #4CAF50; font-weight: bold'transformer(nextState))
          endMessage(logger)
        }
        // 更新prevState
        prevState = nextState
      })
    }
    // 逻辑跟mutation大致相同,不再赘述
    if (logActions) {// 是否打印actions变化
      store.subscribeAction((action, state) => {
        if (actionFilter(action, state)) {
          const formattedTime = getFormattedTime()
          const formattedAction = actionTransformer(action)
          const message = `action ${action.type}${formattedTime}`

          startMessage(logger, message, collapsed)
          logger.log('%c action''color: #03A9F4; font-weight: bold', formattedAction)
          endMessage(logger)
        }
      })
    }
  }
}

// 开始打印
function startMessage (logger, message, collapsed) {
  const startMessage = collapsed// 是否折叠
    ? logger.groupCollapsed// 折叠
    : logger.group// 展开

  // render
  // 考虑到兼容性做了降级
  try {
    startMessage.call(logger, message)
  } catch (e) {
    logger.log(message)
  }
}

// 结束打印
function endMessage (logger) {
// 考虑到兼容性做了降级
  try {
    logger.groupEnd()
  } catch (e) {
    logger.log('—— log end ——')
  }
}

function getFormattedTime () {
  const time = new Date()
  return ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`
}

// String.prototype.repeat的polyfill
function repeat (str, times) {
  return (new Array(times + 1)).join(str)
}

// String.prototype.padStart的polyfill
function pad (num, maxLength) {
  return repeat('0', maxLength - num.toString().length) + num
}

以上就是Vuex4.0源码分析的全部内容了,感谢大家的关注评论点赞和收藏!有缘再会。