This is the 23rd day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Mastering Vuex’s entire data flow step by step through Dispatch

We can see Vuex’s internal workflow step by step through the call of Dispatch, which is the best way to look at the source code. It is much easier to look at the details after we have a clear idea of the main workflow.

Today we will take a look at these Vuex auxiliary functions, which are mapState, mapGetters, mapActions, mapMutations, createNamespacedHelpers. From the name, they are auxiliary functions, which means, We can use Vuex without them, they just make it easier for us to use Vuex.

Note: These helper functions are not available in Vue3’s Compositon API. As we will see in a moment, this is used in all helper functions to access the current component instance. In Setup, this has not been created, so these helper functions cannot be used.

mapState

use

In the component, if we need to access the Vuex, we can calculate the properties

const Counter = {
  template: `<div>{{ count }}</div>`.computed: {
    count () {
      return this.$store.state.count
    }
  }
}
Copy the code

However, when a component needs to obtain multiple states, it will write a lot of calculated attributes. To solve this problem, Vuex provides mapState auxiliary functions to help us generate calculated attributes. You can write less code, such as:

import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // Arrow functions make code more concise
    count: state= > state.count,

    // Pass the string argument 'count' equal to 'state => state.count'
    countAlias: 'count'.// In order to be able to use 'this' to get local state, you must use regular functions
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}
Copy the code

We can also pass mapState an array of strings when the name of the computed property of the map is the same as the name of the child node of State.

computed: mapState([
  // Map this.count to store.state.count
  'count'
])
Copy the code

Similarly, if the component already has other computed properties, we can add them using the object expansion operator

computed: {
  localComputed () { / *... * / },
  // Use the object expansion operator to blend this object into an external object. mapState({// ...})}Copy the code

Principle of inquiry

First, the mapState code is in SRC /helpers.js

This code is actually pretty simple, it’s only about 30 lines, so what does it do

/**
 * 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 () {
      // This function is equivalent to our own calculation attribute function
      // Get state in vuEX
      let state = this.$store.state
      // Get getters in vuex
      let getters = this.$store.getters
      if (namespace) {
        // Obtain the module through namespace
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        // Get module state
        state = module.context.state
        // Get module getters
        getters = module.context.getters
      }
      // val.call, where val is our arrow function state => state.products.all, this is the current component, state is the state of the vuex passed in
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})
Copy the code

The normalizeNamespace function is used to unify namespace and map data into a standard format, and then call the passed function with the standard data as an argument.

/** * 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. To unify namespace and map data into a standard format, and then call the passed function * with the standard data being processed as an argument@param {Function} fn
 * @return {Function}* /
function normalizeNamespace (fn) {
  return (namespace, map) = > {
    if (typeofnamespace ! = ='string') {
      map = namespace
      namespace = ' '
    } else if (namespace.charAt(namespace.length - 1)! = ='/') {
      namespace += '/'
    }
    return fn(namespace, map)
  }
}
Copy the code

NormalizeMap then uses the normalizeMap function to process both array and object data into standard format data, which is annotated clearly, with input parameters and return values.

/**
 * 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] }))
}
Copy the code

The resulting data is returned in the format of an object, as shown below, wrapped by the mapState function

{
  products: function mappedState () {/ *... * /}}Copy the code

MapGetters, mapMutations, mapActions

After looking at the mapState code, mapGetters, mapMutations, and mapActions are almost the same, all of which will use the normalizeNamespace function first to unify the standardized input. The normalizeMap function is then used to uniformly process the data into a standard format. And then finally returns key-value data in an object format, which we can mix in using the object expansion operator.

createNamespacedHelpers

Finally, the helper function, which we use mapState, mapGetters, mapActions, and mapMutations to bind modules with namespaces, can be a bit cumbersome to write:

computed: { ... mapState({a: state= > state.some.nested.module.a,
    b: state= > state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo'.// -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()])}Copy the code

Create helper functions based on a namespace by using createNamespacedHelpers. It returns an object containing the new component binding helper function bound to the given namespace value:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // Search in 'some/nested/module'. mapState({a: state= > state.a,
      b: state= > state.b
    })
  },
  methods: {
    // Search in 'some/nested/module'. mapActions(['foo'.'bar'])}}Copy the code

The principle of the createNamespacedHelpers function is also very simple. Four lines of code bind the namespace parameter in each function through the bind method.

/**
 * Rebinding namespace param for mapXXX function in special scoped, and return them by simple object
 * @param {String} namespace
 * @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)
})
Copy the code

Today we saw how it works with mapState, as well as several other functions, and we can see how it works step by step through breakpoints.

Learn more front-end knowledge together, wechat search [Xiaoshuai’s programming notes], updated every day