相关的util工具方法
- isObject方法,判断obj参数是否是一个对象。
 
export function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}
复制代码
installModule 参数介绍
- store:指store对象实例
 - rootState:根模块的state
 - path:按序(模块嵌套层级)存储模块名的数组。上一章,注册模块有它的介绍。
 - module:模块对象
 - hot:布尔值,同内部变量isRoot一起,用于控制是否使用Vue.set设置state
 
function installModule(store, rootState, path, module, hot) {
    // ...
}
复制代码
installModule 函数逐步分析
定义变量 isRoot 和 namespace
- installModule一旦调用,就会先在函数顶部定义两个变量isRoot和namespace。
 
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
复制代码
- 
isRoot是一个布尔值。若是根模块(path.length = 0),则值为true。若是非根模块(path.length > 0),则值为false。
 - 
namespace是一个字符串。通过调用模块的getNamespace方法返回的,由模块名和’/’拼接而成的字符串。例如:’moduleB/’ 或 ‘moduleB/moduleA/’。关于这个方法的讲解,在上一章中已配合案例做出介绍。
 
在名称空间映射中注册模块
_modulesNamespaceMap 是初始化store实例时定义的一个对象,可理解为——模块名称空间映射表。它专门用来存储带命名空间的模块。
// module.namespaced 为true,则表明此模块开启了命名空间。
// 就是在定义模块时,设置了 namespaced: true的模块。
if (module.namespaced) {
    // __DEV__ 开发环境下为true
    // namespace是否已存在于 _modulesNamespaceMap 中
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
}
复制代码
下图展示的是存储完成后的 _modulesNamespaceMap 对象。

仔细阅读过vuex文档的同学们,应该会很容易理解_modulesNamespaceMap的作用。它同 mapState,mapGetters, mapActions和mapMutations 这些函数有所关联。这些函数的源码都会用到这个对象。当使用这些函数来绑定带命名空间的模块时,为写起来方便,可将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。下面的示例引用自vuex官网文档。
computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}
复制代码
设置 state 对象
这个state指的是根模块的state(this._modules.root.state)。

isRoot 和 hot的值必须同时为false(取反为true),才能设置state。
 if (!isRoot && !hot) {
    // 获取父模块的 state。rootState是根模块的 state,path.slice(0, -1)返回一个
    // 数组,这个数组不包含 path 数组的最后一位元素。
    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('.')}"`
          )
        }
      }
      // 给父模块的state对象添加新属性,这个属性是其子模块名,值是子模块的 state 对象
      Vue.set(parentState, moduleName, module.state)
    })
  }
复制代码
可以看到,在使用Vue.set给父模块的state对象添加新属性时,是在store实例的 _withCommit 方法中调用的。
_withCommit(fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
}
复制代码
这个函数很简单。它先是保存_committing(布尔值,初始化时定义,默认false)的原始值,接着将其设置为true,然后执行fn函数(调用_withCommit时传入的函数,一般都是修改state),当函数执行完毕后,再将_committing设置为原来的值。
可是,这么做有何意义呢?看下面这段代码。
function enableStrictMode(store) {
  store._vm.$watch(function () {
    return this._data.$$state
  }, () => {
    if (__DEV__) {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, {
    deep: true,
    sync: true
  })
}
复制代码
只要开启严格模式,enableStrictMode方法就会被调用。这个方法用Vue中的$watch对this._data.$$state(它指向rootState)进行监听,一旦修改了rootState中的状态且store._committing的值又为false时,它就会抛错。初始化时,默认_committing为false。
但,在源代码中,修改状态是不可避免的。因此,为了防止报错,就把对修改状态的操作,放入_withCommit中执行,因为它会在修改状态前,把_committing设置为true,等修改完成后,再把_committing还原。so,明白否!
makeLocalContext 创建模块上下文环境
const local = module.context = makeLocalContext(store, namespace, path)
复制代码
makeLocalContext会为每个module生成对应的上下文环境。

其中定义了dispatch、commit、getters和state属性。下面是makeLocalContext函数的源码。
function makeLocalContext(store, namespace, path) {
  // 判断命名空间路径是否存在
  const noNamespace = namespace === ''
  // 在local对象中定义dispatch、commit、getters和state,并返回local
  const local = {
    // 根据 namespace 判断。若是true,就直接调用store.dispatch,否则就先
    // 对参数进行处理,然后才调用store.dispatch
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      // unifyObjectStyle 根据_type参数类型对接受的三个参数进行调整(下面会细说)
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options} = args
      let { type } = args
      
      // options.root为true时,将允许在命名空间模块里分发根的 action,否则只分发
      // 当前模块内的action
      
      // options不存在(null或undefined)或 options.root为false,则将命名空间路径
      // 与 type(action函数名)进行拼接。拼接后的字符串:'moduleB/increment',作用
      // 就是可以这样调用:this.$store.dispatch('moduleB/increment')
      if (!options || !options.root) {       
        type = namespace + type
        if (__DEV__ && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }
      return store.dispatch(type, payload)
    },
    // 同dispatch处理方式一样
    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args
      if (!options || !options.root) {
        type = namespace + type
        if (__DEV__ && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }
      store.commit(type, payload, options)
    }
  }
  
  // 给local对象添加getters和state属性
  // getter和state对象必须惰性获取,因为它们将被vm更新更改
  Object.defineProperties(local, {
    getters: {
      get: noNamespace ?
        () => store.getters : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })
  return local
}
复制代码
- unifyObjectStyle 方法
 
在解读这个方法之前,我们需要了解以下两点(均摘自官网):
- Actions 支持载荷方式和对象方式进行分发
 
// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})
// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})
复制代码
- dispatch 使用介绍
 
分发 action。options 里可以有 root: true,它允许在命名空间模块里分发根的 action。
返回一个解析所有被触发的 action 处理器的 Promise。
dispatch(type: string, payload?: any, options?: Object): Promise<any>
dispatch(action: Object, options?: Object): Promise<any>
复制代码
看出来了吗?从上面列出的两点内容,我们可以清楚的知道,action支持两种形式进行分发,且分发时传入的第一个参数类型并不一致。它可以是 String 或 Object。
为了处理这种情况,unifyObjectStyle方法就应运而生了。看下面代码。
function unifyObjectStyle(type, payload, options) {
  // type是对象且type.type为真,则是以对象形式分发,需要要对参数进行调整,否则是载荷形式
  // 分发。且对参数的调整是以载荷形式为准。
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }
  // __DEV__ 开发环境下为true
  if (__DEV__) {
    assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
  }
  // 将参数放在一个对象中返回。
  return {
    type,
    payload,
    options
  }
}
复制代码
- makeLocalGetters 方法
 
// 缓存getters
function makeLocalGetters(store, namespace) {
  // 是否存在缓存
  if (!store._makeLocalGettersCache[namespace]) {
    const gettersProxy = {} // 可理解为getters的代理对象
    const splitPos = namespace.length
    Object.keys(store.getters).forEach(type => {
      // 示例(仅为举例):type ——> 'moduleB/increment'
      // 如果目标getter的名称空间与namespace不匹配,则跳过。
      // 注意:此段代码和registerGetter方法结合看会更容易理解store.getters
      // 中的属性即type,为什么会是:'moduleB/increment'(仅为举例),这种形式。
      if (type.slice(0, splitPos) !== namespace) return
      // 提取getter函数名
      const localType = type.slice(splitPos)
      
      // 为gettersProxy对象定义属性
      Object.defineProperty(gettersProxy, localType, {
        get: () => store.getters[type],
        enumerable: true // 可枚举
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }
  return store._makeLocalGettersCache[namespace]
}
复制代码
- getNestedState 方法
 
获取模块相应的state,此方法的核心在于对数组方法reduce的使用。
function getNestedState(state, path) {
  return path.reduce((state, key) => state[key], state)
}
复制代码
注册mutations、actions 和 getters
关于注册module的mutations、actions和getters这一部分的源码并不复杂,这里不在不在一一介绍,所以同学们要自己尝试调试一下哟。
// 注册module中的mutations、actions和getters,并将其this绑定到当前store对象
  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)
  })
复制代码
注册子模块
使用模块中定义的forEachChild方法,遍历子模块进行注册。
 module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
复制代码
结束语
虽然没有详尽的去解释代码,但不妨碍这文章还是有点东西的。啊哈哈。。。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)