According to vuex source code, to achieve a Vuex, a article with you to write your own vuex.
Hand write a Vuex
We will implement the following main functions of VUEX:
- State: unique data source
- Getter: can be thought of as a computed property of the store. The return value of the getter is cached based on its dependency and recalculated only when the dependency value changes
- Mutation: The only way to change the state in Vuex’s store is to commit mutation
- Action: The action submits a mutation that changes the state of the store
- Module: Splits a store into modules, each with its own state
- Utility functions: mapGetters, mapStates, mapMutations, mapActions
Basic usage of Vuex
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. Vuex official
Let’s initialize a project now
vue create vuex-test cd vuex-test yarn serve
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment() {
this.count++
}
},
actions: {},modules: {}})Copy the code
Let’s implement a basic VUex
myvuex.js
let Vue; / / the vue instance
const install = (_vue) = > {
Vue = _vue;
console.log('install');
}
export default {
install
}
Copy the code
We used our own VUEX in the project to see if it worked. Here we see install printed out, along with an error about the store. To explain the install method, vue calls the provided Install method when using the plug-in and passes in the current Vue instance.
Add the $store attribute to each component
let Vue; / / the vue instance
class Store {}const install = (_vue) = > {
Vue = _vue;
Vue.mixin({
beforeCreate() {
if(this.$options ? this.$options.store) {
this.$store = this.$options.store;
} esle {
this.$store = this.$parent && this.$parent.store; }}})}export default {
install,
Store
}
Copy the code
In this step we will take the store and add the $store property to each component. We know there is only one store, so we just need to return the store of the root component
State is reactive
this.stateData = new Vue({
data() {
return {state: options.state}
}
})
get state() {
return this.stateData.state
}
Copy the code
Vue iterates through the data to make it responsive, changing the view as the data changes. We use this property to do the state response.
Implement getters
let Vue; / / the vue instance
class Store {
constructor(options = {}) {
this.state = options.state;
let getters = options.getters;
this.getters = {}
Object.keys(getters).forEach(getterName= > {
Object.defineProperty(this.getters, getterName, {
get: (a)= > {
return getters[getterName](this.state)
}
})
})
}
}
const install = (_vue) = > {
Vue = _vue;
Vue.mixin({
beforeCreate() {
if(this.$options && this.$options.store) {
this.$store = this.$options.store;
} esle {
this.$store = this.$parent && this.$parent.store; }}})}export default {
install,
Store
}
Copy the code
Realization of mutations
And then the code above
let mutations = options.mutations;
this.mutations = {}
Object.keys(mutations).forEach(mutationName= > {
this.mutations[mutationName] = (payload) = > {
mutations[mutationName](this.state, payload)
}
})
// ES7 guarantees that this points to an instance
commit = (mutationName, payload) = > {
thisMutations [mutationName](payload)} Now let's call it directlythis.$store.commit('countAdd'.10) to achieve the effect of commit.Copy the code
Implement dispatchs
Actions After the asynchronous commit fetch is complete, commit mutation to change the state.
// Defined in store
mutations: {
actionsAdd: (state, payload) = > {
this.state += payload
}
},
actions:
asyncAdd({commit},payload) {
setTimeout((a)= >{
commit('actionsAdd', payload);
}, 1000)}}/ / implementation dispatchs
let actions = optinos.actions;
this.actions = {}
Object.keys(actions).forEach(actionName= > {
this.actions[actionName] = (payload) = > {
actions[actionName](this, payload)
}
})
dispatch = (ActionName, payload) = > {
this.actions[actionName](payload)
}
Copy the code
The enableStrictMode function is used to determine whether the state is changed by mutation. If not, the warning ‘do not mutate vuex store state outside mutation Handlers’ will be raised. So far our store is basically usable, except for Modules.
Implementation modules
Vuex data formatting
// store Modules: {a: {
state: {a: 1}},b: {
state: {b: 1}}}// We need to format the data into the data structure we want,
// So that we can recursively iterate over the data,
// The vuEX data structure is as follows:
// Use the ModuleCollection function to format the data.
let root = {
_raw: optinos,
_chidlren: {
a: {
_raw: {},
_chidlren: {},
state: {a:1}},b: {
_raw: {},
_chidlren: {},
state: {b:1}}},state: options.state
}
let _modules = new ModuleCollection(options)
class ModuleCollection {
constructor(options) {
this.register([], options);
}
register(path, rootModule) {
let module = {
_rawModule: rootModule,
_chidlren: {},
state: rootModule.state
}
// This is the root module
if(path.length === 0) {
this.root = module
} else {
/** * if a: {modules: {c:state: If path=[a], parent = this.root, if path=[a, c], parent = this. _chidLren [a] * This ensures deep nesting. * /
let parent = path.slice(0.- 1).reduce((root, current) = > {
return root._chidlren[current]
}, this.root)
parent._chidlren[path[path.lenght - 1]] = module
}
// Call register recursively to inject submodules
if(rootModule.modules) {
let modules = rootModule.modules
Object.keys(modules).forEach(moduleName= > {
this.register(path.concat(moduleName), modules[moduleName])
})
}
}
}
Copy the code
Data consolidation
We need to put the getters, mutations, and actions of each module together, so now we need to use our formatted _modules data.
// class store
this.getters = {}
this.mutaitons = {}
this.actions = {}
this._modules = new ModuleCollection(options)
installModule(this[],this.state, this._modules.root)
function installModule(Store, rootState, Path, rootModule) {
/** * to handle state, call this.$store.state.a.a * to merge state */
if(path.length > 0) {
let state = path.slice(0.- 1).reduce((root, current) = > {
return rootState[current]
}, rootState)
// Vue cannot simply add nonexistent data to an object, so it is not responsiveVue. Set (rootState, rootState [path [path. The lenght- 1]], state)
}
let getters = rootState._modules.getters
if(getters) {
Objects.keys(getters).forEach((getterName) = > {
Object.defineProperty(getters, getterName, {
get: (a)= > {
return getters[getterName](rootModule.state)
}
})
})
}
* commit = (mutationName, payload) => { * this.mutations[mutationName].forEach((fn) => { * fn(payload) * }) * } */
let mutations = rootState._modules.mutations
if(mutations) {
Objects.keys(mutations).forEach((mutationName) = > {
let mutationNameArr = store.mutations[mutationName] || []
mutationNameArr.push((payload) = > {
mutations[mutationName](rootModule.state, payload)
})
store.mutations[mutationNameArr] = mutationNameArr
})
}
/** ** /
let actions = rootState._modules.actions
if(actions) {
Objects.keys(actions).forEach((actionName) = > {
let actionName = mutations[actionName] || []
actionNameArr.push((payload) = > {
actions[actionName](store, payload)
})
})
}
/** * We recursively register submodules */
let children = rootModule._children
Object.keys(children).forEach((state) = > {
installModule(store, rootState, path.concat(state), children[state])
})
}
Copy the code
Ok, basically, we will achieve the general function of VUex. So let’s implement mapState, mapActions, mapMutations, mapGetters
mapState
/ formatting our data structure, * * * * (' a ', 'b') = > [{key: 'a', val: 'a'}, {key: 'b', val: 'b'}] * {' a ': () = > {}' b ': 11} = > [{key: 'a', val: ()=>{}},{key: 'b', val: 11}] */
function normalizeMap(target) {
return Array.isArray(target)
? target.map(key= > ({key, val: key})
: Object.keys(target).map(key= > ({key, val: target[key])
}
export function mapState(states) {
const result = {}
normalizeMap(states).forEach((state) = > {
result[state.key] = (a)= > {
return typeof state.val === 'function'
? val.call(this.this.$store.state, this.$store.getters)
: this.state[val]
}
})
}
Copy the code
mapGetters
export function mapGetters(getters) {
const result = {}
normalizeMap(states).forEach((getters) = > {
result[getters.key] = (a)= > {
if ((getters.val in this.$store.getters)) {
return this.$store.getters[getters.val]
}
}
return result
})
}
Copy the code
mapActions
export function mapActions (actions) {
const res = {}
normalizeMap(actions).forEach(({ key, val }) = > {
res[key] = function mappedAction (. args) {
return this.$store.dispatch.apply(this.$store, [val].concat(args))
}
})
return res
}
Copy the code
mapMutations
export function mapMutations (mutations) {
const res = {}
normalizeMap(mutations).forEach(({ key, val }) = > {
res[key] = function mappedMutation (. args) {
return this.$store.commit.apply(this.$store, [val].concat(args))
}
})
return res
}
Copy the code
Ok, so basically we’ve implemented all the basic methods of VUEX. Your comments are welcome.