mapState源码分析

383 阅读2分钟

mapState源码分析

  • mapState 工具函数会将 store 中的 state 映射到局部计算属性中。为了更好理解它的实现,先来看一下它的使用示例:

引入官网(vuex.vuejs.org/zh/guide/st…

   // vuex 提供了独立的构建工具函数 Vuex.mapState
  import { mapState } from 'vuex'  
  export default {
    computed: mapState({ 
      // 箭头函数可以让代码非常简洁
      count: state => state.count,
      // 传入字符串 'count' 等同于 `state => state.count`
      countAlias: 'count',
      // 想访问局部状态,就必须借助于一个普通函数,函数中使用 `this` 获取局部状态
      countPlusLocalState (state) {
        return state.count + this.localCount
      }
    })
  }

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

好奇心害死猫 这里是如何实现的呢?你肯定忍不住去搞源码了

/**
 * Reduce the code which written in Vue.js for getting the 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.
 * @param {Object}
 */
export const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  if (__DEV__ && !isValidMap(states)) {
    console.error('[vuex] mapState: mapper parameter must be either an Array or an Object')
  }
  normalizeMap(states).forEach(({ key, val }) => { 
    res[key] = function mappedState () {
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})


/**
 * 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.
 * @param {Function} fn
 * @return {Function}
 */
function normalizeNamespace (fn) {
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
    }
    return fn(namespace, map)
  }
}

第一步:首先入口调用了normalizeMap这个函数,这个函数是个什么东西呢?

/**
 * Normalize the map
 * 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] }))
}


/**
 * Validate whether given map is valid or not
 * @param {*} map
 * @return {Boolean}
 */
function isValidMap (map) {
  return Array.isArray(map) || isObject(map)
}
  1. 调用校验函数 isValidMap 判断是否是数组或则对象
  2. 如果是数组或则对象函数 normalizeMap 处理之后就变成了
  [
    {
      key:value
    }
    ...
  ]
这种形式

例如:
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 } ]

第二步:处理过程分析

 normalizeMap(states).forEach(({ key, val }) => { 
    res[key] = function mappedState () {
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state 
        getters = module.context.getters
      }
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })

处理之后的辅助函数参数处理完之后,执行

mapState({ 
  // 箭头函数可以让代码非常简洁
  count: state => state.count,
  // 传入字符串 'count' 等同于 `state => state.count`
  countAlias: 'count',
  // 想访问局部状态,就必须借助于一个普通函数,函数中使用 `this` 获取局部状态
  countPlusLocalState (state) {
    return state.count + this.localCount
  }
})

转化之后变为

[
  {
    count:state => state.count,
    countAlias:state => state.count,
    countPlusLocalState:(state) {
    return state.count + this.localCount
    }
  }
]

传入的 states 转换成由 {key, val} 对象构成的数组,接着调用 forEach 方法遍历这个数组,构造一个新的对象,这个新对象每个元素都返回一个新的函数 mappedState,函数对 val 的类型判断,如果 val 是一个函数,则直接调用这个 val 函数,把当前 store 上的 state 和 getters 作为参数,返回值作为 mappedState 的返回值;否则直接把 this.$store.state[val] 作为 mappedState 的返回值。

  • 总结

  • 模块化如何使用函数