Vuex辅助API

在我们使用Vuex的过程中,我们可以选择使用Vuex提供的辅助函数来获取我们需要的StateGettersMutations或者Actions,这样我们就不用通过$store来获取了。

// $store获取
this.$store.state.moduleA.count
this.$store.getters.storeCount
this.$store.mutations.moduleA.increment
this.$store.actions.moduleA.increment

// 使用mapXXX获取
mapState('moduleA', ['count'])
mapGetters('moduleA', ['storeCount'])
mapMutations('moduleA', ['increment'])
mapActions('moduleA', ['increment'])

我们可以看到,通过this.$store来获取属性或者方法,我们需要撰写许多同质化的代码。为了更优雅的获取我们想要的属性或者方法,Vuex提供了一些辅助APImapStatemapGettersmapMutationsmapActions

以上这几个辅助API的代码路径在src/helpers.js,其代码如下:

export const mapState = normalizeNamespace((namespace, states) => { ... })
export const mapMutations = normalizeNamespace((namespace, mutations) => { ... })
export const mapGetters = normalizeNamespace((namespace, getters) => { ... })
export const mapActions = normalizeNamespace((namespace, actions) => { ... })

我们可以看到,以上几个方法全部使用normalizeNamespace方法进行包裹,我们先来看一下这个方法的代码:

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)
  }
}

以上代码的处理,主要考虑如下几种使用场景,我们以mapState为例

// 获取主模块的state
mapState(['count'])

// 获取子模块的state
mapState('a', ['count'])

代码分析:

  • 没有命名空间:当不传递命名空间的时候,此时namespace参数就是一个数组,需要调换一下namespace第一个参数和map第二个参数的值,调换完毕后参数值如下:
const namespace = ''
const map = ['count']
  • 有传递命名空间:它先判断namespace最后一个字符是不是/,如果没有携带则默认帮我们添加一个/
  • 对于以上两个例子而言,最后调用fn方法是的参数如下:
// 没有命名空间
fn('', ['count'])

// 有命名空间
fn('a/', ['count'])

另外一个需要注意的地方就是normalizeMap方法,这个方法在以上四个辅助API中都有用到,代码如下:

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] }))
}

function isValidMap (map) {
  return Array.isArray(map) || isObject(map)
}

根据normalizeMap方法的代码我们可以看出:如果参数是数组形式,那么我们map遍历数组,把这个数组处理成key/val对象数组形式;如果参数是对象,那么我们遍历对象的属性键,再调用map遍历数组,把这个数组同样处理成key/val对象数组形式。

这样做主要是为了支持mapXXX辅助API参数的多种形式:数组形式和对象形式。

// 数组形式
mapState(['count'])

// 对象形式
mapState({
  newCount: 'count',
  doubleCount: state => state.count * 2,
  localCount (state) {
    // 此处的this为当前组件实例
    return state.count + this.number
  }
})

mapState

我们首先来看mapState具体实现代码,如下:

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
})

我们以下面两个例子为例进行说明:

// 案例一:主模块state
mapState(['count'])

// 案例二:子模块state
mapState('a', ['count'])

案例分析:

  • mapState调用的时候,它会遍历我们的数组或对象形式参数,然后返回一个新对象,其中新对象的key就是我们提供的参数。假设我们提供如下两种形式的参数:
// 数组形式
mapState(['count'])

// 对象形式
mapState({
  newCount: 'count'
})

根据以上例子,其返回值分别如下:

// 数组形式返回值
const res = { count: function mappedState () { ... } }

// 对象形式返回值
const res = { newCount: function mappedState () { ... } }
  • 案例一:因为我们把mapState返回对象使用...扩展运算符添加到组件的computed计算属性上面去了,所以mappedState函数会在我们获取计算属性的时候开始求值。对于案例一而言,因为我们获取的是主模块的State,因此它直接根据keystore实例上取值并返回即可。

  • 案例二:对于案例二而言,它跟案例一在后面的处理是相同的,只是多了一步根据namespace获取子模块的步骤。

mapGetters

在介绍完mapState后,我们再来看mapGetters方法,代码如下:

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')
  }
  normalizeMap(getters).forEach(({ key, val }) => {
    // The namespace has been mutated by normalizeNamespace
    val = namespace + val
    res[key] = function mappedGetter () {
      // 省略异常处理
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

我们可以看到,相较于mapStatemapGetters实现代码则相对更简单一些,它实质上以上是通过this.$store上去取方法而言。

我们以如下代码为例,来说明mapGetters的返回值:

// 主模块
mapGetters(['storeCount'])
const res = { storeCount: function mappedGetter () { ... } }

// 子模块
mapGetters('a', ['storeCount'])
const res = { 'a/storeCount': function mappedGetter () { ... } }

因为mapGettersmapState都是把得到的res扩展到组件实例的computed计算属性上,所以它们的方法会在使用计算属性的时候调用求值。

mapMutations和mapActions

我们在这一小节把mapMutationsmapActions放在一起,是因为它们两个的实现代码及其相似,只不过mapMutations获取的是commit,而mapActions获取的是dispatch

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')
  }
  normalizeMap(mutations).forEach(({ key, val }) => {
    res[key] = function mappedMutation (...args) {
      // Get the commit method from store
      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
})

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')
  }
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction (...args) {
      // get dispatch function from store
      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
})

我们以如下案例为例,来说明mapMutations的返回结果:

mapMutations('a/', {
  handleIncrement: 'increment'
})
const res = {
  handleIncrement: function mappedMutation () {
    // 省略
  }
}

因为mapMutationsmapActions的返回值会使用...扩展运算符扩展到组件实例methods属性上,所以mappedMutation或者mappedAction会在主动调用方法的时候调用。

this.handleIncrement(100)
最后更新时间:
贡献者: wangtunan