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