A brief introduction.
Vuex is a state management mode developed specifically for vue.js applications. It uses centralized storage to manage the state of all components of an application and rules to ensure that the state changes in a predictable way.
-
Change state with unique entry mutation
-
Vuex cannot be used alone depending on VUE
Let’s implement a Vuex by ourselves.
Two. Vuex module implementation
1. Install method
When the install method is called, passing in the constructor of the current Vue, I save the \_Vue passed in in the Vue variable and export it so that the Vue I am currently using is the same as the Vue I wrote the project to
The ES6 module outputs references to values. When the JS engine statically analyzes a script, it generates a read-only reference to the module load command import. When the script is actually executed, it will be evaluated in the loaded module based on the read-only reference.
let Vue;
export class Store {
constructor(options){}}export const install = (_Vue) = >{
Vue = _Vue;
/ /...
}
Copy the code
2. A mixin method
Qs: How do all components have access to store objects?
Mixin ({beforeCreate}), take store, mount it to itself, take the root component’s store, and share it with each component
Vue.mixin({
beforeCreate() {
let options = this.$options;
if (options.store) {
this.$store = options.store;
} else {
if (this.$parent && this.$parent.$store) {
this.$store = this.$parent.$store; }}}});Copy the code
3. The state
Qs: Change store. State, how to notify view update? Create a vue instance and put store.state on data. The property accessor accesses $store.state proxy to this._vm.$$state
Defining the data passed in by the user on an instance of VUE (this is the VUex core) generates a separate instance of VUE for communication, noting that variables that define the beginning of $are not proxied on the instance
import { Vue } from "./install";
class Store {
constructor(options) {
// The $store used in the user component is equivalent to this
let { state, mutation, actions, moudle, strict, getters } = options;
this._vm = new Vue({
data: {
$$state: state,
},
});
}
get state() {return this._vm._data.$$state
}
}
export default Store;
Copy the code
Because we’re going to iterate multiple times we’re going to write a traversal method over here just to make it easier
export const _forEach = (obj, fn) = > {
Object.keys(obj).forEach((key) = > {
fn(obj[key], key);
});
};
Copy the code
4. Implement getters
Getters in VUex is equivalent to computed in Vue, which does not run by default. Define the data on getters to computed and delegate the value of getters to computed
this.getters = {};
const computed = {}
_forEach(options.getters, (fn, key) = > {
computed[key] = () = > {
return fn(this.state);
}
Object.defineProperty(this.getters,key,{
get:() = > this._vm[key]
})
});
this._vm = new Vue({
data: {
$$state: state,
},
computed // Cache with calculated attributes
});
Copy the code
5. Implement mutations
Commit calls the muations method by iterating the user’s incoming muations to the Store muations
export class Store {
constructor(options) {
this.mutations = {};
_forEach(options.mutations, (fn, key) = > {
this.mutations[key] = (payload) = > fn.call(this.this.state, payload)
});
}
commit = (type, payload) = > {
this.mutations[type](payload); }}Copy the code
6. Implement the actions
Dispatch calls the actions method by iterating the assigned actions from the user to the Store’s Actions
export class Store {
constructor(options) {
this.actions = {};
_forEach(options.actions, (fn, key) = > {
this.actions[key] = (payload) = > fn.call(this.this,payload);
});
}
dispatch = (type, payload) = > {
this.actions[type](payload); }}Copy the code
Three. Implementation of the module mechanism
NameSpaced can resolve the naming conflicts between child and parent modules, adding a namespace
1. Format user data options
The corresponding moudle is placed under the corresponding children and formatted as follows to register the module parent-child relationship using a tree structure
this.root={
_raw: user-defined module,state: State of the current module itself,_children: {a: {_raw: user-defined module,state: State of the current module itself,_children: {// List of submodules}}}}Copy the code
Path. length=0, register as the root module and recursively register modules for the root module
class ModuleCollection {
constructor(options) {
this.root = null;
this.register([], options);
}
register(path, rootModule) {
let newModule = {
_raw: rootModule,
_children: {},
state: rootModule.state,
};
if (path.length == 0) {
this.root = newModule;
} else {
let parent = path.slice(0, -1).reduce((memo, current) = > {
return memo._children[current];
}, this.root);
parent._children[path[path.length - 1]] = newModule;
}
if (rootModule.modules) {
forEach(rootModule.modules, (module, key) = > {
this.register(path.concat(key), module); }); }}}Copy the code
2. Remove the module class
export default class Module {
constructor(rawModule) {
this._raw = rawModule;
this._children = {};
this.state = rawModule.state;
}
getChild(childName) {
return this._children[childName];
}
addChild(childName, module) {
this._children[childName] = module; }}Copy the code
3. Install the module
When there is no namespace, traverse the formatted data and put getters on the root, merge acrions into an array
this._actions = {};
this._mutations = {};
this._wrappedGetters = {};
// Install the module
installModule(this, state, [], this._modules.root);
Copy the code
Provide traversal methods in module classes
addChild(childName, module) {
this._children[childName] = module;
}
forEachGetter(cb) {
this._raw.getters && _forEach(this._raw.getters, cb);
}
forEachMutation(cb) {
this._raw.mutations && _forEach(this._raw.mutations, cb);
}
forEachAction(cb) {
this._raw.actions && _forEach(this._raw.actions, cb);
}
forEachChildren(cb) {
this._children && _forEach(this._children, cb);
}
Copy the code
Install the module
function installModule(store, rootState, path, root) {
if (path.length > 0) {
let parent = path.slice(0, -1).reduce((memo, current) = > {
return memo[current];
}, rootState);
// parent[path[path.length - 1]] = root.state;
// Adding attributes directly to the object does not cause the view to be updated, via vue.set
Vue.set(parent, path[path.length - 1], root.state);
console.log(rootState, "rootState");
}
root.forEachGetter((fn, key) = > {
store.wrapperGetters[key] = function() {
return fn.call(store, root.state);
};
});
root.forEachMutation((fn, key) = > {
store.mutations[key] = store.mutations[key] || [];
store.mutations[key].push((payload) = > {
return fn.call(store, root.state, payload);
});
});
root.forEachAction((fn, key) = > {
store.actions[key] = store.actions[key] || [];
store.actions[key].push((payload) = > {
return fn.call(store, store, payload);
});
});
root.forEachChildren((child, key) = > {
installModule(store, rootState, path.concat(key), child);
});
}
Copy the code
Rewrite the Dispatch and Action methods
commit = (mutationName, payload) = > {
this.mutations[mutationName] &&
this.mutations[mutationName].forEach((fn) = > fn(payload));
};
dispatch = (actionName, payload) = > {
this.actions[actionName] &&
this.actions[actionName].forEach((fn) = > fn(payload));
};
Copy the code
Define state and computed
function resetStoreVM(store, state) {
const computed = {};
store.getters = {};
const wrappedGetters = store._wrappedGetters
_forEach(wrappedGetters, (fn, key) = > {
computed[key] = () = > {
return fn(store.state);
}
Object.defineProperty(store.getters, key, {
get: () = > store._vm[key]
})
});
store._vm = new Vue({
data: {
$$state: state,
},
computed
});
}
Copy the code
5. Implement namespaces
NameSpaced can resolve the naming conflicts between child and parent modules, adding a namespace
Default getters are defined to the parent module if nameSpaced is not present
import { forEachValue } from '.. /util';
import Module from './module';
export default class ModuleCollection {
getNamespace(path) {
let module = this.root
return path.reduce((namespace, key) = > {
module = module.getChild(key);
console.log(module)
return namespace + (module.namespaced ? key + '/' : ' ')},' '); }}export default class Module {
get namespaced() {return!!!!!this._rawModule.namespaced; }}Copy the code
Add a namespace to the binding property
function installModule(store, rootState, path, root) {+let namespace = store._modules.getNamespace(path);
if (path.length > 0) {
let parent = path.slice(0, -1).reduce((memo, current) = > {
return memo[current];
}, rootState);
Vue.set(parent, path[path.length - 1], root.state);
}
root.forEachGetter((fn, key) = > {
+ store.wrapperGetters[namespace + key] = function() {
return fn.call(store, root.state);
};
});
root.forEachMutation((fn, key) = > {
+ store.mutations[namespace + key] = store.mutations[namespace + key] || [];
+ store.mutations[namespace + key].push((payload) = > {
return fn.call(store, root.state, payload);
});
});
root.forEachAction((fn, key) = > {
+ store.actions[namespace + key] = store.actions[namespace + key] || [];
+ store.actions[namespace + key].push((payload) = > {
return fn.call(store, store, payload);
});
});
root.forEachChildren((child, key) = > {
installModule(store, rootState, path.concat(key), child);
});
}
Copy the code
6. Register modules
To register a module, register the current module with _modules
registerModule(path, module) {
if (typeof path == "string") path = [path];
this._modules.register(path, module);
installModule(this.this.state, path, module.newModule);
restVM(this.this.state);
}
Copy the code
Reset the instance
function restVM(store, state) {
let oldVm = store._vm;
const computed = {};
store.getters = {};
forEach(store.wrapperGetters, (getter, key) = > {
computed[key] = getter;
Object.defineProperty(store.getters, key, {
get: () = > store._vm[key],
});
});
store._vm = new Vue({
data: {
$$state: state,
},
computed,
});
if (oldVm) {
// Destroy the last instance created
Vue.nextTick(() = >oldVm.$destroy()); }}Copy the code
4. Realize plug-in function
store = new Vuex.Store({
plugins: [logger(), persists("localStorage")],)}Copy the code
1. Realize the logger
Vuex comes with a Logger plug-in. It’s super easy to write a logger plug-in by hand
function logger() {
return function(store) {
let prevState = JSON.stringify(store.state);
store.subscribe((mutation, state) = > {
console.log("prevState:", prevState);
console.log("mutation:", mutation);
console.log("currentState:".JSON.stringify(state));
prevState = JSON.stringify(state);
});
};
}
Copy the code
2. Implement persistent plug-ins
Refresh the data and re-initialize the vuex. Now let’s implement a plugin on our own
function persists() {
return function(store) {
let localState = JSON.parse(localStorage.getItem("VUEX:STATE"));
if (localState) {
store.replaceState(localState);
}
store.subscribe((mutation, rootState) = > {
// You need to do a throttle-lodash
localStorage.setItem("VUEX:STATE".JSON.stringify(rootState));
});
};
}
Copy the code
3. Implement plugins, subscribe, and replaceState
class Store {
constructor(options) {
this._modules = new ModuleCollection(options);
installModule(this, state, [], this._modules.root);
/ /...
+ if (options.plugins) {
options.plugins.forEach((plugin) = > plugin(this)); }} +subscribe(fn) {
this._subscribes.push(fn);
}
+ replaceState(state){
this._vm._data.$$state = state;
}
/ /...
}
function installModule(store, rootState, path, root) {
let namespace = store._modules.getNamespace(path);
/ /...
root.forEachMutation((fn, key) = > {
store.mutations[namespace + key] = store.mutations[namespace + key] || [];
store.mutations[namespace + key].push((payload) = > {
fn.call(store, root.state, payload);
+ store._subscribes.forEach((fn) = >
fn({ type: namespace + key, payload }, rootState)
);
});
});
/ /...
}
Copy the code
4. Obtain the latest status
function getNewState(store, path) {
return path.reduce((memo, current) = > {
return memo[current];
}, store.state);
}
function installModule(store, rootState, path, root) {
let namespace = store._modules.getNamespace(path);
/ /...
root.forEachGetter((fn, key) = > {
store.wrapperGetters[namespace + key] = function() {+return fn.call(store, getNewState(store, path));
};
});
root.forEachMutation((fn, key) = > {
store.mutations[namespace + key] = store.mutations[namespace + key] || [];
store.mutations[namespace + key].push((payload) = > {
+ fn.call(store, getNewState(store, path), payload);
store._subscribes.forEach((fn) = >
fn({ type: namespace + key, payload }, store.state)
);
});
});
}
Copy the code
5. Auxiliary functions
1. The mapState implementation
function mapState(stateList) {
let obj = {};
stateList.forEach((stateName) = > {
obj[stateName] = function() {
return this.$store.state[stateName];
};
});
return obj;
}
Copy the code
2. MapGetters implementation
function mapGetters(gettersList) {
let obj = {};
gettersList.forEach((getterName) = > {
obj[getterName] = function() {
return this.$store.getters[getterName];
};
});
return obj;
}
Copy the code
3. MapMutations implementation
function mapMutations(mutationList) {
let obj = {};
mutationList.forEach((mutationName) = > {
obj[mutationName] = function(payload) {
this.$store.commit(mutationName, payload);
};
});
return obj;
}
Copy the code
4. MapActions implementation
function mapActions(actionList) {
let obj = {};
actionList.forEach((actionName) = > {
obj[actionName] = function(payload) {
this.$store.dispatch(actionName, payload);
};
});
return obj;
}
Copy the code
Mutation and action
Strict modestrict: true
, failed to passmutation
Modify thestate
When,vuex
The following error message is displayed
“We do this internally by maintaining a variable (_research) to determine if this is happening in mutaion
_withCommitting(fn) {
this._committing = true;
fn(); (" this function is synchronous (true), (" get _research (false) if fn is asynchronous (")
this._committing = false;
}
Copy the code
Added synchronous Watcher in strict mode to monitor state changes
if (store.strict) {
store._vm.$watch(
() = > store._vm._data.$$state,
() = > {
console.assert(
store._committing,
"' State changed outside mutation '"
);
},
{ deep: true.sync: true}); }Copy the code
Internal change status is a normal update, so you need to wrap this with _withresearch too
store._withCommitting(() = > {
Vue.set(parent, path[path.length - 1].module.state);
})
Copy the code
replaceState(newState){\this._withCommitting(() = > {
this._vm._data.$$state = newState; })}Copy the code