Related util tool methods
- IsObject method that checks whether the obj argument is an object.
export function isObject (obj) {
returnobj ! = =null && typeof obj === 'object'
}
Copy the code
InstallModule Parameter Description
- Store: indicates the store object instance
- RootState: indicates the state of the root module
- Path: An array that stores module names in order (module nesting level). The registration module was introduced in the previous chapter.
- Module: Module object
- Hot: A Boolean value that, along with the internal variable isRoot, controls whether state is set with vue.set
function installModule(store, rootState, path, module, hot) {
// ...
}
Copy the code
Step by step analysis of installModule functions
Define the variables isRoot and namespace
- Once called, the installModule first defines two variables isRoot and namespace at the top of the function.
constisRoot = ! path.lengthconst namespace = store._modules.getNamespace(path)
Copy the code
-
IsRoot is a Boolean value. For the root module (path.length = 0), the value is true. If it is a non-root module (path.length > 0), the value is false.
-
Namespace is a string. The string returned by calling the module’s getNamespace method and concatenating the module name with a ‘/’. For example, ‘moduleB/’ or ‘moduleB/moduleA/’. As for the explanation of this method, the case has been introduced in the previous chapter.
Register modules in the namespace map
_modulesNamespaceMap is an object defined when the Store instance is initialized and can be understood as a module namespace mapping table. It is specifically used to store modules with namespaces.
// module.namespaced is true, indicating that the module has namespace enabled.
// modules with namespaced: true are defined.
if (module.namespaced) {
// __DEV__ is true in the development environment
// Whether the namespace already exists in _modulesNamespaceMap
if (store._modulesNamespaceMap[namespace] && __DEV__) {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
store._modulesNamespaceMap[namespace] = module
}
Copy the code
The following figure shows the _modulesNamespaceMap object after it has been stored.
Those of you who have perused the vuex documentation should easily understand what _modulesNamespaceMap does. It’s associated with functions like mapState, mapGetters, mapActions, and mapMutations. The source code for these functions uses this object. When you use these functions to bind a module with a namespace, you can pass the module’s space name string as the first argument to the above function to make it easier to write, so that all bindings automatically use the module as a context. The following example is from the vuex official documentation.
computed: { ... mapState('some/nested/module', {
a: state= > state.a,
b: state= > state.b
})
},
methods: {
...mapActions('some/nested/module'['foo'.// -> this.foo()
'bar' // -> this.bar()])}Copy the code
Setting the State object
This state refers to the state of the root module (this._modules.root.state).
To set state, both isRoot and hot must be false (inversely true).
if(! isRoot && ! hot) {// Get the state of the parent module. RootState is the state of the root module, path.slice(0, -1) returns one
// Array that does not contain the last element of the PATH array.
const parentState = getNestedState(rootState, path.slice(0, -1))
// Submodule name
const moduleName = path[path.length - 1]
store._withCommit(() = > {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('. ')}"`)}}// Add a new attribute to the state object of the parent module. This attribute is the name of the child module and the value is the state object of the child module
Vue.set(parentState, moduleName, module.state)
})
}
Copy the code
As you can see, when vue.set is used to add new properties to the state object of the parent module, it is called in the _withCommit method of the Store instance.
_withCommit(fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
Copy the code
This function is very simple. This saves the raw value of _research (Boolean (initialization), default false), sets it to true, and then executes the fn function (the function passed in when calling _withCommit, usually changing state). When this function is done, Set _research to the original value.
But what’s the point? Look at the code below.
function enableStrictMode(store) {
store._vm.$watch(function () {
return this._data.$$state
}, () = > {
if (__DEV__) {
assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)}}, {deep: true.sync: true})}Copy the code
The enableStrictMode method is called whenever strict mode is enabled. This method listens on this._data.$$state (which points to rootState) with $watch in Vue, and once you modify the rootState and store. __research value is false, it will throw an error. Case of initialization, default _research is false.
However, in source code, changing state is unavoidable. So, to prevent errors, put the state change operation in _withCommit (right), because it sets _research to true (right) before changing the state, and then restore _research (right) after the change. So, understand?
MakeLocalContext creates the module context
const local = module.context = makeLocalContext(store, namespace, path)
Copy the code
MakeLocalContext generates the corresponding context for each module.
The Dispatch, COMMIT, getters, and State properties are defined. Here is the source code for the makeLocalContext function.
function makeLocalContext(store, namespace, path) {
// Determine whether the namespace path exists
const noNamespace = namespace === ' '
// Define dispatch, commit, getters, and state in the local object and return local
const local = {
// Determine by namespace. If true, call store.dispatch directly, otherwise first
// Parameters are processed before store.dispatch is called
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) = > {
// unifyObjectStyle adjusts the three accepted parameters based on the _type parameter type (more on that below)
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options} = args
let { type } = args
// options.root allows distribution of root actions in namespace modules when true, otherwise only
// Action in the current module
// if options does not exist (null or undefined) or options.root is false, the namespace path will be changed
// Concatenate with type (action function name). Concatenated string: 'moduleB/increment', used
$store. Dispatch ('moduleB/increment')
if(! options || ! options.root) { type = namespace + typeif(__DEV__ && ! store._actions[type]) {console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
return}}return store.dispatch(type, payload)
},
// Same as dispatch
commit: noNamespace ? store.commit : (_type, _payload, _options) = > {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if(! options || ! options.root) { type = namespace + typeif(__DEV__ && ! store._mutations[type]) {console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
return
}
}
store.commit(type, payload, options)
}
}
// Add the getters and state attributes to the local object
Getter and state objects must be lazily fetched because they will be changed by VM updates
Object.defineProperties(local, {
getters: {
get: noNamespace ?
() = > store.getters : () = > makeLocalGetters(store, namespace)
},
state: {
get: () = > getNestedState(store.state, path)
}
})
return local
}
Copy the code
- UnifyObjectStyle method
Before interpreting this approach, there are two things to know:
- Actions supports both payload and object distribution
// Distribute in payload form
store.dispatch('incrementAsync', {
amount: 10
})
// Distribute as objects
store.dispatch({
type: 'incrementAsync'.amount: 10
})
Copy the code
- Dispatch Introduction
The distribution of the action. Options can have root: true, which allows distribution of root actions in the namespace module. Return a Promise that resolves all action handlers that are triggered.
dispatch(type: string, payload? : any, options? :Object) :Promise<any>
dispatch(action: Object, options? :Object) :Promise<any>
Copy the code
Can you see that? From the two points listed above, it is clear that action can be distributed in two forms, and that the first parameter type passed in for distribution is not the same. It can be a String or an Object.
To handle this, the unifyObjectStyle method was created. Look at the code below.
function unifyObjectStyle(type, payload, options) {
// If type is an object and type.type is true, it is distributed as an object. Parameters need to be adjusted, otherwise it is a payload
/ / distribution. And the adjustment of parameters is based on the form of load.
if (isObject(type) && type.type) {
options = payload
payload = type
type = type.type
}
// __DEV__ is true in the development environment
if (__DEV__) {
assert(typeof type === 'string'.`expects string as the type, but found The ${typeof type}. `)}// Put the argument in an object.
return {
type,
payload,
options
}
}
Copy the code
- MakeLocalGetters method
/ / the cache getters
function makeLocalGetters(store, namespace) {
// Whether cache exists
if(! store._makeLocalGettersCache[namespace]) {const gettersProxy = {} // can be interpreted as a proxy object for getters
const splitPos = namespace.length
Object.keys(store.getters).forEach(type= > {
// Example (only for example) : type -- > 'moduleB/increment'
// If the namespace of the target getter does not match the namespace, skip it.
// Note: this code is used with the registerGetter method to make it easier to understand store.getters
ModuleB /increment = 'moduleB/increment'
if (type.slice(0, splitPos) ! == namespace)return
// Extract the name of the getter function
const localType = type.slice(splitPos)
// Define attributes for the gettersProxy object
Object.defineProperty(gettersProxy, localType, {
get: () = > store.getters[type],
enumerable: true / / can be enumerated
})
})
store._makeLocalGettersCache[namespace] = gettersProxy
}
return store._makeLocalGettersCache[namespace]
}
Copy the code
- GetNestedState method
Obtain the corresponding state of the module. The core of this method is the use of the array method reduce.
function getNestedState(state, path) {
return path.reduce((state, key) = > state[key], state)
}
Copy the code
Register for mutations, Actions, and getters
The source code for registration module mutations, Actions and getters is not complicated. It is not introduced here, so students should try to debug it by themselves.
Register mutations, actions, and getters in the Module and bind this to the current store object
module.forEachMutation((mutation, key) = > {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
module.forEachAction((action, key) = > {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
module.forEachGetter((getter, key) = > {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
Copy the code
Registration submodule
Use the forEachChild method defined in the module to traverse the submodule for registration.
module.forEachChild((child, key) = > {
installModule(store, rootState, path.concat(key), child, hot)
})
Copy the code
conclusion
This article has something to offer, although it doesn’t explain the code in detail. Lol…