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.