Skip to content

namespace

javascript
function installModule (store, rootState, path, module, hot) {
  // 不开启命名空间, getters 不能重名,type 相同的 action 和 mutation 会被合并在
  // 一个数组中,dispatch 时相同 tpye 的 action 都会执行
  // commit 时 相同 type 的 mutation 都会执行
  // 假设 b 模块的父模块是 a 模块,a 模块和 b 模块都开启命名空间
  // a 模块下 action、mutation 的 type 前都会加上 a/,getter 的 key 前会加上 a/
  // b 模块下 action、mutation 的 type 前都会加上 a/b/,getter 的 key 前会加上 a/b/
  // 如果 a 模块还有子模块 c、子模块 d,如果 c、d 模块不开启命名空间
  // 那么c、d 模块 type 前只会加上 a/,getter 的 key 前只会加上 a/
  // a、c、d 模块 type 相同的 action 和 mutation 会被合并在一个数组中 
  // a、c、d 模块的 getter 不能重名

  // 因此安装模块时第一步是获取 namespace,如果有的话 action、mutation、getter 的
  // type 都需要加上 namespace,dispatch 或 commit 时如果有 namespace 就也在 tppe
  // 前加上 namespace,这样就能实现隔离。因此 local.dispatch 和 local.commit 属性
  // 的值需要判断 namespace 是否为空字符串,如果是空字符串,没有开启命名空间,值就是
  // store.dispath,否则开启了命名空间,值是包装函数,内部让
  // type = namespace + type,最后执行 store.dispath(type, payload),就能找到
  // namespace 下的数组,
  // local.getters 也需要判断 namespace 是否为空字符串,如果是空字符串,没有开启命名
  // 空间,值就是 store.getters,否则开启命名空间,值是一个对象,对象里的每个 key
  // 都是以 namespace 开头的 getter 的 key,为了避免每次都从所有 getters 中查找以 
  // namespace 开头的 getter 然后再放进对象中,通过
  // store._makeLocalGettersCache 对象缓存查找结果。对象的 key 是 namespace
  // value 是一个对象 getterProxy,保存着所有以该 namespace 开头的 getter。
  // getterProxy 的 key 就是我们定义的 getter 的 key,最前面不会有 namespace,
  // 这样我们在查看 local.getters 时就不会有 namespace 干扰了,value 就是
  // store.getters[type] 对应的 wrappedGetter。gettersProxy 实现了劫持,当访问
  // 我们本地定义的 key 时才会获取对应的 wrappedGetter。保证总是获取到最新的 getter。
  // 因此获取 namespace 下的 getter 时,如果
  // store._makeLocalGettersCache[namespace] 存在,直接返回 getterProxy。
  // 如果不存在,先获取 namespace 的长度,Object.keys(store.getters) 获取所有
  // getter 的名字,遍历每一项并 slice(0, namespace.length) 开始截取名字前缀,
  // 如果截取出来前缀和 namespace 不相等说明这个 getter 不是 namespace 下的 getter,
  // 否则是这个namespace 下的 getter,需要保存进 getterProxy 中,
  // 通过 Object.defineProperty 为 getterProxy 添加,key 是我们本地写的 key,不带
  // namespace,而 registerGetter 时加上了 namespace,因此 
  // slice(namespace.length) 获取我们本地定义时的 getter 的 key,
  // value 就是 store.getters[type]。遍历完后就找出了所有该 namespace 下的所有
  // getter 了,最后让store._makeLocalGettersCache[namespace] = gettersProxy。
  // state 不受到 namespace 影响,因此无需处理。

  // 如路径 ['a', 'b'] 中 a 模块开启命名空间,b 模块不开启命名空间那么这个路径的命名
  // 空间就是 /a,如果 b 模块开启命名空间那么这个路径的命名空间就是 /a/b。
  // Module 类中定义一个 namespaced 属性标识是否开启命名空间,
  // 访问它时返回 this._rawModole.namespaced。
  // ModuleCollection 类中定义一个 getNameSpace 方法,传入 path 拼接处该路径的 
  // namespace,内部从根模块开始不断向下找,直到找到路径中最后一个模块,reduce 初始值
  // 是 '',过程中发现当前模块开启了命名空间的话结果就会拼接上 'moduleName/',
  // 否则拼接 '',最后将这个结果返回就是 namespace 的值,
  const namespace = store._modules.getNamespace(path)

  // local(module.context) 是一个对象,保存着当前模块的
  // dispatch、commit、getters、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 namespacedType = namespace + key
    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)
  })
}

function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  const local = {
    dispatch: noNamespace ? store.dispatch : (type, payload) => {
      type = namespace + type
      if (__DEV__ && !store._actions[type]) {
        return
      }

      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      type = namespace + type
      if (__DEV__ && !store._mutations[type]) {
        return
      }

      store.commit(type, payload, options)
    }
  }

  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

// 创建获取 local.getters 时的缓存表
function makeLocalGetters (store, namespace) {
  if (!store._makeLocalGettersCache[namespace]) {
    const gettersProxy = {}
    const splitPos = namespace.length
    Object.keys(store.getters).forEach(type => {
      // 这个 getter 的 key 不是以 namespace 开头
      // skip if the target getter is not match this namespace
      if (type.slice(0, splitPos) !== namespace) return

      // 本地定义的 getter 的 key
      // extract local getter type
      const localType = type.slice(splitPos)

      // 保存这个结果,劫持保证获取的总是最新的 getter
      // Add a port to the getters proxy.
      // Define as getter property because
      // we do not want to evaluate the getters in this time.
      Object.defineProperty(gettersProxy, localType, {
        get: () => store.getters[type],
        enumerable: true
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }

  return store._makeLocalGettersCache[namespace]
}

class Module {
  get namespaced () {
    return !!this._rawModule.namespaced
  }
}

class ModuleCollection {
  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }
}