preface

  • Vuex official user guide document
  • Vuex source address
  • Vuex is a state manager developed specifically for Vue because it is developed internally based on Vue
  • stateStorage state,gettersProcess state to the outside world
  • mutationsThe only way to change state,actionsAsynchronous operations
  • modulesModular management, when there are too many states can be divided into modules
  • strictStrict mode, non – canonical writing will report errors
  • pluginsPlug-in use, such as data persistence, or modify data to print the log (internal logger)

Principle of State implementation (Description)

  • Reassemble a structure tree (internally recursive) by processing the parameters passed in by the user
  • Recursively assemble all the states (the most external state and the state in modules) into a state tree and put it into a unified variablelet state
  • throughnew Vue({ data: { $$state: state } }), component rendering, data collection Rendering watcher(publish and subscribe), data changes to the page for updating
  • Description The following is the main example

Structure tree and state tree


Getters implementation Principle (Description)

  • Put all getters passed in by the user into a variablelet warpperGetters
  • I’m going to loop it throughlet computedAnd hijack the agentObject.defineProperty
  • Allow users to passthis.$store.getter.xxxxAccess to the
  • The last onnew Vue({ computed })
  • Below is the core andcomputedThe final structure of

store.getters = {}
const computed = {}
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
})
Copy the code

Principles of mutations and Actions implementation (description)

  • They are also all the parameters that will be passed by the userMutations and actionsPut them in their respective variables
  • If the method name is duplicated it will be put in an array and then called in a loop (see picture below)
  • Users must use it in their own waycommit.dispatch(See code below)
  • Users can access thethis.$store.commit('xxx', params)orthis.$store.commit('xxx/xxxx', params)Access to the
  • Users can access thethis.$store.dispatch('xxx', params)orthis.$store.dispatch('xx/xxx', params)Access to the
  • The difference is that mutations are the only changestateAnd the operation is synchronous
  • And actions are asynchronous, so if you want to change itstateIt’s also submitted internallycommitmethods

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

Modules

  • The main thing to do is modularity
  • There’s actually a namespace insidenamespaced: truewillstateTo this modulekeyCreate an object
  • Put this module’sstatePut it in, and finally put it in the state tree, and then modularize the data
  • So the user gets the value throughthis.$store.state.a.xxx(See state map above)
  • Mutations and actionsIs to change the method name toxx/xxx(see the mutations and Actions diagram above), the method is modular in turn

Strict implementation Principle (Description)

  • Is it strict modetrueIs strict mode
  • Internally, it looks like an internal method by looking at data changes
  • The internal methods are wrapped_withCommitttingmethods
  • Look at the code snippet below
// The internal modations method is wrapped with _withCommittting
store.mutations[ns + key].push((payload) = > {
    / / look here
    store._withCommittting(() = > {
        fn.call(store, getNewState(store, path), payload)
    })
})

// Default is false
this._committing = false

/** Strict mode to monitor the state through synchronous watcehr deep observation */ 
"/** store. _research (false) implies an error */ (wrong) 
if (store.strict) {
    store._vm.$watch(() = > store._vm._data.$$state, () = > {
        // sync = true Changing watcher to synchronous state changes will be executed immediately not asynchronous watcher
        console.assert(store._committing, 'No mutate in mutation handler outside method is not allowed to write outside')
        // All attributes are iterated internally
    }, { deep: true.sync: true})}/ * * *@description If strict mode is enabled, an error is reported if the writing method is not specified *@description This method is wrapped around an internal stack of modified data *@description Research ($store.state. XXX = 'XXX') without wrapping this. _research = false *@description "This._research (always false)" (error */)
_withCommittting(fn) {
    this._committing = true
    fn()
    this._committing = false
}
Copy the code

Plugins implementation Principles (Description)

  • Take for example the logger
  • Subscribe to the methods in the plug-insubscribe
  • Finally, it was released on mutations
  • Take a look at the code snippet below

// Whether plug-ins are used
if (options.plugins) {
    options.plugins.forEach(plugin= > plugin(this))}/ * * *@description Subscribe to the * /
subscribe(fn) {
    this._subscribes.push(fn)
}

/ / release
store.mutations[ns + key].push((payload) = > {
    store._withCommittting(() = > {
        fn.call(store, getNewState(store, path), payload) Subscirbe is performed after mutation
    })

    // Publish logger when data changes
    store._subscribes.forEach(fn= > fn({ type: ns + key, payload }, store.state))
})

Copy the code

Why does every component get $store

  • Put user-created stores into vue instances
  • inVue.use(Vuex)The install method is executed when
  • throughVue.mixinExecute in beforeCreated (inside is an array called parent -> child -> grandchild before data hijacking)
  • But onlynew VueIn thestoreComponent and parent exist instoreThe component is
  • This allows all of its components to be registered$storeThe instance
  • othernew VueThere is no
  • Take a look at the code snippet below
// main.js
let vm = new Vue({
  store, // The purpose of this store is to make the store object accessible to all components
  render: h= > h(App)
}).$mount('#app')

// install.js
export let Vue
function install(_Vue) {
    Vue = _Vue

    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

Implementation principle of auxiliary Function (Description)

  • mapState, mapGetters, mapMutations, mapActions
  • It’s a layer of agency
  • Take a look at the code snippet below
// helper.js
/ * * *@description Auxiliary function mapState */
 export function mapState(stateList) {
    let obj = {}
    for (let i = 0; i < stateList.length; i++) {
        let stateName = stateList[i]
        obj[stateName] = function() {
            return this.$store.state[stateName]
        }
    }
    return obj
}

/ * * *@description The auxiliary function mapMutations */
export function mapMutations(mutationList) {
    let obj = {}
    for (let i = 0; i < mutationList.length; i++) {
        obj[mutationList[i]] = function (payload) {
            this.$store.commit(mutationList[i], payload)
        }
    }
    return obj
}

Copy the code

The directory structure of the project

*Generated by the vue/cli vue2 project ├ ─ ─ public │ └ ─ ─ index. The HTML ├ ─ ─ the SRC │ ├ ─ ─ store │ │ └ ─ ─ index. The js │ ├ ─ ─ vuex │ │ └ ─ ─ the module │ │ │ ├ ─ ─ The module - collection. Js │ │ │ └ ─ ─ the module. The js │ │ ├ ─ ─ helpers. Js │ │ ├ ─ ─ index. The js │ │ ├ ─ ─ the js │ │ ├ ─ ─ store. Js │ │ ├─ ├─ class.org.txtCopy the code

The sample

src/store/index.js

import Vue from 'vue'
import Vuex from '@/vuex'

// VuEX internal logger
// import logger from 'vuex/dist/logger.js'

Vue.use(Vuex)

/** Plug-in method (simple handling) */
/ * * *@description Each update of the data is logged *@description State changes are all via mutation using commit() to commit other invalid */
function logger() {
    return function(store) {
        let prevState = JSON.stringify(store.state)
        store.subscribe((mutation, state) = > {
            console.log('prevState:' + prevState)
            console.log('mutation:' + JSON.stringify(mutation))
            console.log('currentState:' + JSON.stringify(state))
            prevState = JSON.stringify(state)

        })
    }
}

/ * * *@description Page refresh data does not reset to persist *@description Put it in localhost */
function persists() {
    return function(store) {
        let localState = JSON.parse(localStorage.getItem('VUEX:STATE'))

        // Replace old data
        if (localState) {
            store.replaceState(localState)
        }

        // Every data change is stored in localStorage
        store.subscribe((mutation, rootState) = > {
            localStorage.setItem('VUEX:STATE'.JSON.stringify(rootState))
        })
    }
}

let store = new Vuex.Store({
    plugins: [ logger(), persists() ],

    strict: true.state: {
        name: 'zhangsan'.age: 2
    },

    getters: {
        myAge(state) {
            return state.age + 5}},mutations: {
        changeAge(state, payload) {
            state.age += payload
        }
    },

    actions: {
        changeAge({ commit }, payload) {
            setTimeout(() = > {
                commit('changeAge', payload);
            }, 1000); }},/ * * *@description The name of a submodule cannot be the same as the state * in the parent module@description Namespaced (emphatically) can resolve the naming conflicts between child and parent modules by adding a separate namespace *@description If you don't have namespaced default getters will be defined on the parent module *@description Mutations that don't have Namespaced mutations will be combined and eventually called * together@description Namespaces eliminate this problem */
    modules: {
        a: {
            namespaced: true.state: {
                name: 'a module'.age: 1
            },
            getters: {
                aAge(state) {
                    return state.age + 10; }},mutations: {
                changeAge(state, payload) {
                    state.age += payload
                }
            },
            modules: {
                c: {
                    namespaced: true.state: {
                        age: 100
                    },
                    mutations: {
                        changeAge(state, payload) {
                            state.age += payload
                        }
                    }
                }
            }
        },

        b: {
            state: {
                name: 'b module'.age: 2.gender: 'male'
            },
            getters: {
                bAge(state) {
                    return state.age + 10; }},mutations: {
                changeAge(state, payload) {
                    state.age += payload
                }
            }
        }

    }
    
})

export default store

Copy the code

src/App.vue

<template>
  <div id="app">
    <h3>Not modules</h3>

    <h5>Name and Age:</h5>
    <span>{{name}} - {{age}}</span>
    <hr>

    <h5>Getters age:</h5>
    <span>{{this.$store.getters.myAge}}</span>
    <br>
    <button @click="$store.commit('changeAge',10)">Change the age</button>
    <button @click="$store.dispatch('changeAge',10)">Asynchronous age</button>
    <button @click="$store.state.name='xxxx'">$store.state.name=' XXXX</button>
    <hr>


    <h3>Is the modules</h3>

    <h5>State name age of module A</h5>
    <p>Obtain method state.a.age</p>
    <span>{{this.$store.state.a.name}} - {{this.$store.state.a.age}}</span>
    <br>

    <h5>Getters age for module A (with Namespaced)</h5>
    <p>Getters ['a/aAge']</p>
    <span>{{this.$store.getters['a/aAge']}}</span>
    <br>
    <button @click="$store.commit('a/changeAge',10)">Commit ('a/changeAge',10)</button>
    <hr>

    <h5>B module state name age gender (no namespaced)</h5>
    <p>Obtain method state.b.age</p>
    <span>{{this.$store.state.b.name}} - {{this.$store.state.b.age}} - {{this.$store.state.b.gender}}</span>
    <br>
    <button @click="$store.commit('changeAge',10)">Module B changes the age commit('changeAge',10), the access method overrides, and the peripheral state changes</button>
    <br>

    <h5>State age for a/ C modules (with Namespaced)</h5>
    <p>How to obtain state.a.c.age</p>
    <span>{{this.$store.state.a.c.age}}</span>
    <br>
    <button @click="$store.commit('a/c/changeAge',10)">Commit (' A/C /changeAge',10)</button>
    <hr>


    <h3>User registration module</h3>
    <span>
      {{this.$store.state.rModule && this.$store.state.rModule.name}} -
      {{this.$store.state.rModule && this.$store.state.rModule.number}} -
      {{this.$store.getters.rGetterNumber && this.$store.getters.rGetterNumber}}
    </span>
    <br>
    <button @click="registerModule">Manual registration module</button>

  </div>
</template>

<script>
import store from './store'

import { mapState } from './vuex/index'

export default {
  name: 'app'.computed: {
    ...mapState(['name'.'age'])},methods: {
    registerModule() {
      store.registerModule('rModule', {
        state: {
          name: 'rModule'.number: 5,},getters: {
          rGetterNumber(state) {
            return state.number+5}})},},}</script>

<style>
#app span {
  color: blue;
}

button {
  height: 35px;
  line-height: 35px;
  padding:0 10px;
  margin-top: 10px;
  margin-right: 10px;
  color: #fff;
  background-color: # 000;
  outline: none;
  border: 0;
}
</style>

Copy the code

To the chase

src/vuex/index.js

import install from './install'
import Store from './store'
import { mapState, mapGetters, mapMutations, mapActions } from './helpers'

export {
    Store,
    install,
    mapState,
    mapMutations,
    mapGetters,
    mapActions
}

export default {
    install,
    Store,
    mapState,
    mapGetters,
    mapMutations,
    mapActions
}

Copy the code

src/vuex/install.js

export let Vue

function install(_Vue) {
    Vue = _Vue

    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
                }
            }
        }
    })

}

export default install

Copy the code

src/vuex/store.js

import { Vue } from './install'
import ModuleCollection from './module/module-collection'
import { forEach } from './util'

/ * * *@description Get the latest state(data hijacked) *@description For example, click inside to refresh the screen to get the latest value */
function getNewState(store, path) {
    return path.reduce((memo, current) = > {
        return memo[current]
    }, store.state)
}

** * Install the module wrapperGetters Actions mutations */
function installModule(store, rootState, path, module) {
    // a/b/c
    let ns = store._modules.getNamespace(path)

    if (path.length > 0) {

        / / to find his father
        let parent = path.slice(0, -1).reduce((memo, current) = > {
            return memo[current]
        }, rootState)

        // New object properties cannot cause views to be updated
        store._withCommittting(() = > {
            Vue.set(parent, path[path.length - 1].module.state)
        })

    }

    module.forEachGetter((fn, key) = > {
        store.wrapperGetters[ns + key] = function() {
            return fn.call(store, getNewState(store, path))
        }
    })

    module.forEachMutation((fn, key) = > {
        store.mutations[ns + key] = store.mutations[ns + key] || []
        store.mutations[ns + key].push((payload) = > {
            store._withCommittting(() = > {
                fn.call(store, getNewState(store, path), payload) Subscirbe is performed after mutation
            })

            // Publish logger when data changes
            store._subscribes.forEach(fn= > fn({ type: ns + key, payload }, store.state))
        })
    })

    module.forEachAction((fn, key) = > {
        store.actions[ns + key] = store.actions[ns + key] || []
        store.actions[ns + key].push((payload) = > {
            return fn.call(store, store, payload)
        })
    })

    module.forEachChildren((child, key) = > {
        installModule(store, rootState, path.concat(key), child)
    })
}

/ * * *@description Re-register the VM */
function resetVM(store, state) {
    let oldVm = store._vm
    store.getters = {}
    const computed = {}
    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
    })

    /** Strict mode to monitor the state through synchronous watcehr deep observation */ 
    "/** store. _research (false) implies an error */ (wrong) 
    if (store.strict) {
        store._vm.$watch(() = > store._vm._data.$$state, () = > {
            // sync = true Changing watcher to synchronous state changes will be executed immediately not asynchronous watcher
            console.assert(store._committing, 'No mutate in mutation handler outside method is not allowed to write outside')
            // All attributes are iterated internally
        }, { deep: true.sync: true})}// After re-creating the instance, the old instance needs to be uninstalled
    if (oldVm) {
        Vue.nextTick(() = > oldVm.$destroy())
    }

}

/ * * *@description Integrate user modules */
class Store {
    constructor(options) {
        // Format the user's parameters
        this._modules = new ModuleCollection(options)

        this.wrapperGetters = {}

        // Collect all getters, mutations,actions in the module
        this.mutations = {}
        this.actions = {}
        this._subscribes = []
        
        // The default is not changed in mutation
        this._committing = false
        
        // Is there a strict mode
        this.strict = options.strict
        
        
        // No Namespaced getters are placed at the root
        Mutations and Actions will merge arrays
        let state = options.state
        installModule(this, state, [], this._modules.root)
        
        resetVM(this, state)

        // Whether plug-ins are used
        if (options.plugins) {
            options.plugins.forEach(plugin= > plugin(this))}}/ * * *@description If strict mode is enabled, an error is reported if the writing method is not specified *@description This method is wrapped around an internal stack of modified data *@description Research ($store.state. XXX = 'XXX') without wrapping this. _research = false *@description "This._research (always false)" (error */)
    _withCommittting(fn) {
        this._committing = true
        fn()
        this._committing = false
    }

    / * * *@description Subscribe to the * /
    subscribe(fn) {
        this._subscribes.push(fn)
    }

    / * * *@description Replace state */
    replaceState(newState) {
        this._withCommittting(() = > {
            this._vm._data.$$state = newState
        })
    }

    / * * *@description Gets the current state */
    get state() {
        return this._vm._data.$$state
    }

    / * * *@description $store.com MIT Dispatch publishing */
    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))
    }

    / * * *@description User registration module */
    registerModule(path, module) {
        if (typeof path == 'string') path = [path]

        // Module is written directly by the user
        this._modules.register(path, module)

        // Re-install the user's module
        installModule(this.this.state, path, module.newModule)

        Vuex internal re-registration will regenerate the instance
        // Although the reinstallation only solved the state problem, computed was lost
        // Destroy redo
        resetVM(this.this.state)
    }

}

export default Store

Copy the code

src/vuex/module/module-collection.js

import { forEach } from '.. /util'
import Module from './module'

/** ** ** ** ** ** ** ** ** ** *@description Root = {* _RAW: user defined module, * state: state of the current module, * _children: {list of children * A: {* _RAW: user defined module, * state: Their own state, the current module * _children:} {child list * e: {} * *}, * c:}} {} * * * * * * * * * * * * * * * * * * * * * * * * /
class ModuleCollection {
    constructor(options) {
        this.root = null

        // Core method
        this.register([], options)
    }

    / * * *@description Concatenate module names if modules have namespaced *@description [a,b,c] -> 'a/b/c'
     */
    getNamespace(path) {
        let root = this.root

        let ns = path.reduce((ns,key) = > {
           let module =  root.getChild(key) 
           root = module;
           return module.namespaced ? ns + key + '/' : ns
        }, ' ')

        return ns
    }

    / * * *@description Registration modules encapsulate modules */
    register(path, rawModule) {
        let newModule = new Module(rawModule)
        rawModule.newModule = newModule
        if (path.length == 0) {
            this.root = newModule
        } else {

            /** Find father */
            let parent = path.slice(0, -1).reduce((memo, current) = > {
                return memo.getChild(current)
            }, this.root)

            /** Register it with the child of the corresponding module */ based on the currently registered key
            parent.addChild(path[path.length-1], newModule)

        }

        /** Register root module recursion */ 
        if (rawModule.modules) {
            forEach(rawModule.modules,(module,key) = > {
               this.register(path.concat(key), module)})}}}export default ModuleCollection

Copy the code

src/vuex/module/module.js

import { forEach } from ".. /util"

class Module {
    constructor(rawModule) {
        this._raw = rawModule
        this._children = {}
        this.state = rawModule.state
    }

    / * * *@description Get the child element module */
    getChild(childName) {
        return this._children[childName]
    }

    / * * *@description Add the child element module */
    addChild(childName, module) {
        this._children[childName] = module
    }

    / * * *@description Loop getters Mutations Actions for collection */
    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)
    }

    / * * *@description Loop _children for recursive collection getters mutations Actions */
    forEachChildren(cb) {
        this._children && forEach(this._children, cb)
    }

    / * * *@description Have you written namespaced */
    get namespaced() {
        return!!!!!this._raw.namespaced
    }

}

export default Module

Copy the code

src/vuex/util.js

/ * * *@description Loop objects and enforce methods */
export const forEach = (obj, fn) = > {
    Object.keys(obj).forEach(key= > {
        fn(obj[key], key)
    })
}

Copy the code

src/vuex/helpers.js

/ * * *@description Auxiliary function mapState */
 export function mapState(stateList) {
    let obj = {}
    for (let i = 0; i < stateList.length; i++) {
        let stateName = stateList[i]
        obj[stateName] = function() {
            return this.$store.state[stateName]
        }
    }
    return obj
}

/ * * *@description The auxiliary function mapGetters */
export function mapGetters(gettersList) {
    let obj = {}
    for (let i = 0; i < gettersList.length; i++) {
        let getterName = gettersList[i]
        obj[getterName] = function() {
            return this.$store.getters[getterName]
        }
    }
    return obj
}

/ * * *@description The auxiliary function mapMutations */
export function mapMutations(mutationList) {
    let obj = {}
    for (let i = 0; i < mutationList.length; i++) {
        obj[mutationList[i]] = function (payload) {
            this.$store.commit(mutationList[i], payload)
        }
    }
    return obj
}

/ * * *@description The auxiliary function mapActions */
export function mapActions(actionList) {
    let obj = {}
    for (let i = 0; i < actionList.length; i++) {
        obj[actionList[i]] = function (payload) {
            this.$store.dispatch(actionList[i], payload)
        }
    }
    return obj
}

Copy the code

After the