Use cases of Vuex
const store = createStore({
state: {},
mutations: {},
actions: {},
getters: {},
modules: {
childModule1: {
namespaced: true.state: {},
mutations: {},
actions: {},
getters: {},
modules: {
grandsonModule: {
namespaced: true.state: {},
mutations: {},
actions: {},
getters: {},},},},childModule2: {
namespaced: true.state: {},
mutations: {},
actions: {},
getters: {},},},});Copy the code
When using Vuex, multiple modules are often used. So how does Vuex register and process these modules?
Registration and collection of modules
class Store {
constructor(options) {
const store = this;
// Collection module
store._modules = newModuleCollection(options); }}Copy the code
ModuleCollection
This class is mainly used to handle nested modules, converting the user’s modules into a tree structure within Vuex
class ModuleCollection {
constructor(rootModule) {
this.root = null;
this.register(rootModule, []);
}
register(rawModule, path) {
const newModule = new Module(rawModule);
if (path.length == 0) {
// represents a root module
this.root = rawModule;
} else {
// Get the first few items in the path except for the last item, starting from root to get the child
// Because the last item represents the current module
const parent = path.slice(0, -1).reduce((module, cur) = > {
return module.getChild(cur);
}, this.root);
// Mount the current module to the corresponding module in the path path
parent.addChild(path[path.length - 1], newModule);
}
// If the module has submodules, register the submodules
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) = > {
// Register the submodule and concatenate the module's path (the module's key)
this.register(rawChildModule, path.concat(key)); }); }}}Copy the code
The realization of the Module
class Module {
constructor(rawModule) {
this._raw = rawModule;
this._children = {};
this.state = rawModule.state;
this.namespaced = rawModule.namespaced;
}
getChild(key) {
return this._children(key);
}
addChild(key, module) {
this._children[key] = module;
}
forEachChild(fn) {
forEachValue(this._children, fn);
}
forEachGetter(fn) {
if (this._raw.getters) {
forEachValue(this._raw.getters.fn); }}forEachMutation(fn) {
if (this._raw.mutations) {
forEachValue(this._raw.mutations, fn); }}forEachAction(fn) {
if (this._raw.actions) {
forEachValue(this._raw.actions, fn); }}}Copy the code
After processing, the Module will end up with the following data structure
store: { _modules: { root: { _raw: rootModule, state: rootModule.state, _children: { childModule1: { _raw: childModule1, state: childModule1.state, _children: { grandsonModule: { _raw: childModule2, state: grandsonModule.state, _children: {}, }, }, }, childModule2: { _raw: childModule2, state: childModule2.state, _children: {},},},}}}Copy the code
Handle modules’ getters, mutations, actions
Register all module’s getters, mutations, actions on the Store instance
store._wrappedGetters = Object.create(null);
store._mutations = Object.create(null);
store._actions = Object.create(null);
// Define the state
const state = store._modules.root.state; / / state
//
installModule(store, state, [], store._modules.root);
Copy the code
function getNestedState(state, path){
return path.reduce((curState, key) = > curState[key], state)
}
function installModule(store, state, path, module){
constisRoot = ! path.length;if(! isRoot){ }// {double: function(state){return state.count * 2}}
module.forEachGetter((getter, key) = >{
store._wrappedGetters[key] = () = > {
return getter(getNestedState(store.state, path))
}
})
// mutation is publish-subscribe based and may have multiple callback functions
// {add: [mutation, mutation, ...] }
module.forEachMutation((mutation, key) = > {
const entry = store._mutations[key] || (store._mutations[key] = [])
entry.push((payload) = > { // store.commit("add", payload)
mutation.call(store, getNestedState(store.state, path), payload)
})
})
The action returns a promise after the mutation is performed
module.forEachAction((action, key) = > {
const entry = store._actions[key] || (store._actions[key] = [])
entry.push((payload) = > {
let res = action.call(store, store, payload)
// Determine if res is a promise
if(! isPromise(res)) {return Promise.resolve(res)
}
return res
})
})
// If there are submodules, install submodules
module.forEachChild((child, key) = > {
installModule(store, rootState, path.concat(key), child)
})
}
Copy the code
Namespace handling
Namespaced indicates whether the namespace is enabled or not
Add a method getNamespaced to the ModuleCollection class to get the namespace
// [a,c] => a/c
getNamespaced(path){
let module = this.root;
return path.reduce((nameSpaceStr, key) = >{
module = module.getChild(key); // Get submodules
return nameSpaceStr + (module.namespaced ? key + "/" : "")},"")}Copy the code
Add the namespace for getters, mutations, and Actions
const namespaced = store._modules.getNamespaced(path)
// store.getters["some/nested/module/foo"]
module.forEachGetter((getter, key) = > {
store._wrappedGetters[namespaced + key] = () = > {
// ...}})// store.commit("some/nested/module/foo", payload)
module.forEachMutation((mutation, key) = > {
const entry = store._mutations[namespaced + key] || (store._mutations[namespaced + key] = [])
// ...
})
// store.dispatch("some/nested/module/foo", payload)
module.forEachAction((action, key) = > {
const entry = store._actions[namespaced + key] || (store._actions[namespaced + key] = [])
// ...
})
Copy the code
resetStoreState
Proxy getter, and handles the state response
function resetStoreState(store, state){
Date = 'XXX' without affecting the response of the data
store._state = reactive({data: state})
const wrappedGetters = store._wrappedGetters;
store.getters = {};
forEachValue(wrappedGetters, (getter, key) = > {
Object.defineProperty(store.getters, key, {
get: getter,
enumerable: true})})}Copy the code
Turn on strict mode
In options, you can specify whether to start strict mode or not. After this mode is enabled, you can only change the state at commit time. Other changes are illegal and will cause errors.
The basic idea of strict pattern implementation
- Add a state before mutation, _commiting = true
- Call mutation => change the status, monitor the status, if the current status changes, _commiting = true, synchronize the change
- _commiting = false
- If _commiting = false, the status changes, the modification is illegal
class Store{
this.strict = options.strict || false;
this._commiting = false;
resetStoreState(store, state)
_withCommit(fn){ // Slice mode programming
const commiting = this._commiting;
this._commiting = true;
// The only valid operation is to change the state in fn
fn();
this._commiting = commiting;
}
commit = (type, payload) = > {
const entry = this._mutations[type] || [];
this._withCommit(() = >{
entry.forEach(handler= > handler(payload))
})
}
dispatch = (type, payload) = > {
const entry = this._actions[type] || []
return Promise.all(entry.map(handler= > handler(payload)))
}
}
function resetStoreState(store, state){
if(store.strict){
enableStrictMode(store)
}
}
function enableStictMode(store){
watch(() = >store._state.data, () = >{
console.assert(store._commiting, "State cannot be modified outside mutation")}, {deep: true.flush: 'sync'})}Copy the code
useStore
export const storeKey = 'store'
import {inject} from 'vue'
// createApp().use(store, 'my')
class Store{
install(app, injectKey) {
// globally exposes a variable that exposes an instance of store
app.provide(injectKey || storeKey, this)
// Vue2.x Vue.prototype.$store = this
app.config.globalProperties.$store = this; // Add the $store attribute so that you can use $store.state.count directly in the template}}// These apis are already exported from vue
// const store = useStore("my")
export function useStore(injectKey = null) {
returninject(injectKey ! = =null ? injectKey : storeKey)
}
Copy the code