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
state
Storage state,getters
Process state to the outside worldmutations
The only way to change state,actions
Asynchronous operationsmodules
Modular management, when there are too many states can be divided into modulesstrict
Strict mode, non – canonical writing will report errorsplugins
Plug-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 variable
let state
- through
new 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 variable
let warpperGetters
- I’m going to loop it through
let computed
And hijack the agentObject.defineProperty
- Allow users to pass
this.$store.getter.xxxx
Access to the - The last on
new Vue({ computed })
- Below is the core and
computed
The 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 user
Mutations and actions
Put 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 way
commit
.dispatch
(See code below) - Users can access the
this.$store.commit('xxx', params)
orthis.$store.commit('xxx/xxxx', params)
Access to the - Users can access the
this.$store.dispatch('xxx', params)
orthis.$store.dispatch('xx/xxx', params)
Access to the - The difference is that mutations are the only change
state
And the operation is synchronous - And actions are asynchronous, so if you want to change it
state
It’s also submitted internallycommit
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
Modules
- The main thing to do is modularity
- There’s actually a namespace inside
namespaced: true
willstate
To this modulekey
Create an object - Put this module’s
state
Put it in, and finally put it in the state tree, and then modularize the data - So the user gets the value through
this.$store.state.a.xxx
(See state map above) Mutations and actions
Is 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 mode
true
Is strict mode - Internally, it looks like an internal method by looking at data changes
- The internal methods are wrapped
_withCommittting
methods - 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-in
subscribe
- 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
- in
Vue.use(Vuex)
The install method is executed when - through
Vue.mixin
Execute in beforeCreated (inside is an array called parent -> child -> grandchild before data hijacking) - But only
new Vue
In thestore
Component and parent exist instore
The component is - This allows all of its components to be registered
$store
The instance - other
new Vue
There 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