This article mainly explains how Vuex is implemented at the bottom level, so that we can really understand and understand the internal implementation mechanism of Vuex. How to read it? Knock out the bottom vuex hand yourself. First of all,State in Vuex is implemented using responsiveness in Vue
And second,Use publish subscriptions to implement mutations and actions, and Object.defineProperty to implement getters
Finally, againDelve into modules implementation mechanisms
. The article may be a little long, as long as you carefully read, will certainly gain.
If you think it is helpful for you to learn VUEX, I hope you can order a small onepraiseOh!!!!!! Ok, no more words, officially enter the article theme!!
Vuex concept
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. In fact, Vuex is a container for storing state. Each component can obtain the state of the shared component through Vuex, which is actually the communication process between components. The operation process of Vuex is shown below:
Core principles of Vuex
Vuex
和Vue-Router
It’s all the same, it’s all a plugin for Vue,Essence is an object
.Vuex
Object has two properties, one isThe install method
, one isStore the class
.The install method
Is the function ofMount the Store instance to each component
.Store
In this class, containsCommit, dispatch
And so on, through the internalNew Vue to use Vue's response to achieve Vuex's internal response
.
Ok, now that we know how vuex works internally, are we going to implement Vuex
The install method
The install method basically makes each component mount an instance of $Store. This.$store allows each component to obtain the state and methods in Vuex.
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store/store.js'
Vue.config.productionTip = false
new Vue({
store,
render: h= > h(App),
}).$mount('#app')
Copy the code
const install = (_Vue) = > {
Vue = _Vue
applyMinx(Vue)
}
const applyMinx = (Vue) = > {
// Each component needs to be mixed with the mount $store property
Vue.mixin({
beforeCreate:vuexInit
})
}
// The child component is created after the parent
function vuexInit () {
const options = this.$options
// Bind $store to the root component
if(options.store) {
this.$store = options.store
} else if (options.parent && options.parent.$store) {
// There is a parent component and the parent component has the $store property
// The non-root component fetches $store from the parent component to bind to its own component instance
this.$store = options.parent.$store
}
}
Copy the code
VuexInit ($options) {vuexInit ($options) {vuexInit ($options) {vuexInit ($options) {vuexInit ($options); $options.store ($options.store); $options.store ($options.store); $options.store ($options.store); If there is no store attribute, it indicates that the child component has a parent component and the parent component has $store attribute. Then add $store attribute to the child component.
Store the class
import Vue from 'vue'
import Vuex from '.. /vuex/index.js'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
age: 10
},
mutations: {
changeAge(state,num) {
state.age += num
}
},
getters: {
getAge(state) {
return state.age
}
},
modules: {},
actions: {
changeAge({commit},payload) {
setTimeout(() = >{
commit('getAge',payload)
},1000)}}})Copy the code
import applyMinx from './mixin'
let Vue
class Store {
constructor(options) {
$store. State can render data on the component, but the data is not reactive
this.state = options.state
}
}
Copy the code
// App.vue
<template>
<div id="app">
state: {{$store.state.age}}
<button @click="handle">Click on the + 10</button>
</div>
</template>
<script>
export default {
name: 'App'.methods: {
handle() {
this.$store.state.age += 10
console.log(this.$store.state.age); }}}</script>
Copy the code
$store. State (mutation) : $store. State (mutation) : $store.
Test results:
And then you see that you can render it to the page normally, and you can render it to the page, but,Now this data is not reactive
That is, when I change the value of state in store with a button click, at this pointWill not trigger page re-rendering, data update
, it is also aDifferences between Vuex and global variables
(Interview test points).Vuex is responsive
And theGlobal variables are not reactive
. Speaking of the difference between Vuex and global variables, let’s briefly explain the difference between Vuex and global variables:
Differences between Vuex and global variables
- The state store for Vuex is
responsive
. When the Vue component reads the state from the store, ifstore
The state changes in, then the correspondingComponents are efficiently updated accordingly
. At the same timeYou cannot modify store directly
In, onlyChange the state by showing commit mutation
. - Vuex by
Unified approach to modifying data
Global variables doAny modification
. - Too many global variables can cause
Namespace pollution
, Vuex won’t, meanwhileVuex also addresses the issue of cross-component communication
.
State
Through analysis, we know that the above method of directly assigning options.state to this.state is not available, so we need to set the state in store as responsive. Here vuex cleverly makes use of the responsive principle of Vue. Responsiveness is achieved by storing the state in store into the VUE instance as an attribute of data. The code is as follows:
import applyMinx from './mixin'
let Vue
class Store {
constructor(options) {
$store. State can render data on the component, but the data is not reactive
let state = options.state
this._vm = new Vue({
// Define data in vue. If the attribute name is $XXX, it will not be proxied to the vue instance and stored in _data
// If you don't want to get it from this._vm.state, use $to get it from this._vm_data
data() {
return {// $$internal status
$$state: state
}
},
})
}
// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// The data is responsive
return this._vm._data.$$state
}
}
Copy the code
$Store. State = $Store. State = $Store. $Store. State = $Store. Vue has internally hijacked the data in data to achieve responsiveness. When accessing vUE instance objects, the instance object data will also be responsiveness. Test results:
And what we found by testing is that the data at this point is zeroresponsive
And whenWhen the store state changes
, the page will also render the new value.
Getters
With state implemented, let’s move on to implementing Vuex’s getters method. When using vuex getters, have you noticed that the declared getters are a method, but when we use vuex getters, we can directly write the attribute value to get the data?
// Write the method.getters: {
getAge(state) {
return state.age
}
},
...
Copy the code
// Get attributes
<template>
<div id="app">
state: {{$store.state.age}}
getters: {{$store.getters.getAge}}
<button @click="handle">Click on the + 10</button>
</div>
</template>
Copy the code
DefineProperty (object.defineProperty); getGetMethod (object.defineProperty);
import applyMinx from './mixin'
// import forEach from './utils.js'
let Vue
class Store {
constructor(options) {
$store. State can render data on the component, but the data is not reactive
let state = options.state
// Use the same method as in store
this._getters = {}
// the method written by getters gets attributes
GetAge is the key function. GetAge is the property of the value getmethod
Object.keys(options.getters).forEach(key= > {
Object.defineProperty(this.getters,key,{
get:() = > {
return options.getters[key](this.state)
}
})
})
this._vm = new Vue({
// Define data in vue. If the attribute name is $XXX, it will not be proxied to the vue instance and stored in _data
// If you don't want to get it from this._vm.state, use $to get it from this._vm_data
data() {
return {// $$internal status
$$state: state
}
},
})
}
// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// This will be reactive
return this._vm._data.$$state
}
}
Copy the code
In the code above. We declare a _getters Object on the Store class to Store the getters method in the Store, iterate over the key of the getters in the Store, place it on _getters, and then use Object.defineProperty to delegate. When accessing the getter property, the get method is triggered and the options.getters function is automatically executed. It will return it. Test results:
If you test it, you’ll see that getters can be displayed on the page and implementedresponsive
This is because inGet the getters property
Triggered whenget
And theGet executes a function that contains this.state
, soState implements responsiveness
Indirect getters are also implementedresponsive
.
But hereGetters has no caching effect
If the value of the dependency has not changed, the method in getters will be executed. The tests are as follows:
.state: {
age: 10.a: 1
},
getters: {
getAge(state) {
console.log('getAge executes. ');
return state.age
}
},
...
Copy the code
<template>
<div id="app">
state.age: {{$store.state.age}}
satte.a:{{$store.state.a}}
getters: {{$store.getters.getAge}}
<button @click="handle">Click on a + 10</button>
</div>
</template>
<script>
export default {
name: 'App'.methods: {
handle() {
// Change only the value of a
this.$store.state.a += 10}}}</script>
Copy the code
The age value does not change, but the getAge method of getters is always executed becauseThe data for state has changed
, will update the page and in the pageAlways get the getAge value of getters
And at this pointAlways trigger the GET method
, re-executemethod
This is not what we want to see. So, we need toCache getters
. So how do you cache? To review whether some of vUE’s computed data has caching effects,Vuex makes clever use of vUE's computed caching feature
To solveVuex getters cache
Question, so this is what we often say,vuex
theGetters properties
Is equivalent tovue
In theComputed properties
The state attribute of vuex is equivalent to data in VUE, in factVuex internally instantiates a VUE
And thenGetters uses VUE's computed to implement caching
, andState is implemented by using the data response in VUE
. (You may be asked why we often say that vuex state and getters are data and computed in VUE, and what do you think of that?)
Implement getters cache
import applyMinx from './mixin'
// import forEach from './utils.js'
let Vue
class Store {
constructor(options) {
// console.log(options);
$store. State can render data on the component, but the data is not reactive
let state = options.state
// object.create differs from {} :
// object.create (null) The prototype chain points to null {} The prototype chain points to Object
// Store the user-defined getters
this.getters = Object.create(null)
// The cache computes attributes using vUE's computed attributes and puts its own attributes on the instance
const computed = {}
// the method written by getters gets attributes
GetAge is the key function. GetAge is the property of the value getmethod
Object.keys(options.getters).forEach(key= > {
// Cache uses vUE to compute attributes for lazy loading
computed[key] = () = > {
return options.getters[key](this.state)
}
Object.defineProperty(this.getters,key,{
get:() = > {
return this._vm[key]
}
})
})
this._vm = new Vue({
// Define data in vue. If the attribute name is $XXX, it will not be proxied to the vue instance and stored in _data
// If you don't want to get it from this._vm.state, use $to get it from this._vm_data
data() {
return {// $$internal status
$$state: state
}
},
computed This._vm. A is the a that gets the calculated property})}// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// This will be reactive
return this._vm._data.$$state
}
}
const install = (_Vue) = > {
Vue = _Vue
applyMinx(Vue)
}
export {
Store,
install
}
Copy the code
A computed property in vUE puts its own attributes on the VUE instance, which can be passed directly through the instance. Property to get the value for computed. Test results:
Perfect, we implemented getters cache effect.
Mutations
After completing the function of getters, we will continue to improve the methods of mutations. Through the publishing and subscription model in Vuex, the mutations and actions defined by users will be stored, and the subscription solutions will be found when the COMMIT is triggered. Look for the subscription Actions method when dispatch is triggered.
import applyMinx from './mixin'
// import forEach from './utils.js'
let Vue
class Store {
constructor(options) {
$store. State can render data on the component, but the data is not reactive
let state = options.state
// object.create differs from {} :
// object.create (null) The prototype chain points to null {} The prototype chain points to Object
// Store the user-defined getters
this.getters = Object.create(null)
// The cache computes attributes using vUE's computed attributes and puts its own attributes on the instance
const computed = {}
// the method written by getters gets attributes
GetAge is the key function. GetAge is the property of the value getmethod
Object.keys(options.getters).forEach(key= > {
// Cache uses vUE to compute attributes for lazy loading
computed[key] = () = > {
return options.getters[key](this.state)
}
Object.defineProperty(this.getters,key,{
get:() = > {
return this._vm[key]
}
})
})
this._vm = new Vue({
// Define data in vue. If the attribute name is $XXX, it will not be proxied to the vue instance and stored in _data
// If you don't want to get it from this._vm.state, use $to get it from this._vm_data
data() {
return {// $$internal status
$$state: state
}
},
computed This._vm. A is the a that gets the calculated property
})
Publish and subscribe stores user-defined mutations and actions looking for the subscribe solutions method when commit is called
// Call Dispatch and find the subscribe Actions method
/ / method of mutations
this._mutations = Object.create(null)
Object.keys(options.mutations).forEach(key= > {
// Publish subscribe mode
this._mutations[key] = (playload) = > {
// The first argument points to state
// call ensures that this always points to the current store instance
options.mutations[key].call(this.this.state,playload)
}
})
}
// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// This will be reactive
return this._vm._data.$$state
}
commit = (type,playload) = > {
// triggering commit triggers the method in _mutations
this._mutations[type](playload)
}
}
const install = (_Vue) = > {
Vue = _Vue
applyMinx(Vue)
}
export {
Store,
install
}
Copy the code
// APP.vue
<template>
<div id="app">
state.age: {{$store.state.age}}
satte.a:{{$store.state.a}}
getters: {{$store.getters.getAge}}
<button @click="handle">Click on a + 10</button>
<button @click="$store.commit('changeAge',5)">Mustation method</button>
</div>
</template>
Copy the code
.mutations: {
changeAge(state,num) {
state.age += num
}
},
...
Copy the code
Actions
The Actions method is the same as the mutations method, which stores the subscription by publishing it, and then finds the actions method of the subscription when the dispatch is triggered.
import applyMinx from './mixin'
// import forEach from './utils.js'
let Vue
class Store {
constructor(options) {
$store. State can render data on the component, but the data is not reactive
let state = options.state
// object.create differs from {} :
// object.create (null) The prototype chain points to null {} The prototype chain points to Object
// Store the user-defined getters
this.getters = Object.create(null)
// The cache computes attributes using vUE's computed attributes and puts its own attributes on the instance
const computed = {}
// the method written by getters gets attributes
GetAge is the key function. GetAge is the property of the value getmethod
Object.keys(options.getters).forEach(key= > {
// Cache uses vUE to compute attributes for lazy loading
computed[key] = () = > {
return options.getters[key](this.state)
}
Object.defineProperty(this.getters,key,{
get:() = > {
return this._vm[key]
}
})
})
this._vm = new Vue({
// Define data in vue. If the attribute name is $XXX, it will not be proxied to the vue instance and stored in _data
// If you don't want to get it from this._vm.state, use $to get it from this._vm_data
data() {
return {// $$internal status
$$state: state
}
},
computed This._vm. A is the a that gets the calculated property
})
Publish and subscribe stores user-defined mutations and actions looking for the subscribe solutions method when commit is called
// Call Dispatch and find the subscribe Actions method
/ / method of mutations
this._mutations = Object.create(null)
Object.keys(options.mutations).forEach(key= > {
// Publish subscribe mode
this._mutations[key] = (playload) = > {
// The first argument points to state
// call ensures that this always points to the current store instance
options.mutations[key].call(this.this.state,playload)
}
})
/ / actions
this._actions = Object.create(null)
Object.keys(options.actions).forEach(key= > {
this._actions[key] = (playload) = > {
// The first argument will be this because here we always deconstruct {commit}
options.actions[key].call(this.this, playload)
}
})
}
// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// This will be reactive
return this._vm._data.$$state
}
commit = (type,playload) = > {
// triggering commit triggers the method in _mutations
this._mutations[type](playload)
}
dispatch = (type,playload) = > {
this._actions[type](playload)
}
}
const install = (_Vue) = > {
Vue = _Vue
applyMinx(Vue)
}
export {
Store,
install
}
Copy the code
//App.vue
<template>
<div id="app">
state.age: {{$store.state.age}}
satte.a:{{$store.state.a}}
getters: {{$store.getters.getAge}}
<button @click="handle">Click on a + 10</button>
<button @click="$store.commit('changeAge',5)">Mustation method</button>
<button @click="$store.dispatch('changeAge',10)">Method the actions</button>
</div>
</template>
Copy the code
.actions: {
changeAge({commit},playload) {
setTimeout(() = >{
commit('getAge',playload)
},1000)}}...Copy the code
We can see that the actions change is executed after 1s, which is consistent with the timer we wrote in store, and it’s doneFunction of the actions
The implementation of the.
At this point, vuex’s main implementation mechanism is complete, but modules is not implemented yet, but vuex’s main logic is already running. Now that WE’re 70% of the way through vuex, let’s explore how modules are implemented
Modules Basic usage
Before we write modules by hand, let’s take a look at the basic usage of modules in VUex.
import Vue from 'vue'
// import Vuex from '.. /vuex/index.js'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
age: 10.a: 1
},
mutations: {
changeAge(state,num) {
state.age += num
}
},
getters: {
getAge(state) {
console.log('getAge executes. ');
return state.age
}
},
actions: {
getAge({commit},playload) {
setTimeout(() = >{
commit('getAge',playload)
},1000)}},modules: {
b: {
state: {
c:100
},
mutations: {
changeAge() {
console.log('C in B is updated')}}},d: {
state: {
e:100
},
mutations: {
changeAge() {
console.log(The e in'd 'is updated.)}}}}})Copy the code
Mutations method add two submodules, B template and D module, to modules, and the mutations method inside is the same name as the mutations of the root module. See the test result:
And what you find is,By default, when mutations have the same name, there is no scope between modules
, where the function is combinedDon't cover
Instead, the function is stored in an array. whenTo trigger a commit
“Will be in turnExecute the functions in the array
.
Add a submodule E to module D, and you will find that the name of module D is the same as the name of module E under state. Test results:
// APP.vue
<template>
<div id="app">State.age: {{$store.state.age}} getters: {{$store.getters. GetAge}} b module c {{$store.state.b.c}} D module E {{$store.state.d.e}}<button @click="handle">Click on a + 10</button>
<button @click="$store.commit('changeAge',5)">Mustation method</button>
<button @click="$store.dispatch('changeAge',10)">Method the actions</button>
</div>
</template>
Copy the code
import Vue from 'vue'
// import Vuex from '.. /vuex/index.js'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
age: 10,},mutations: {
changeAge(state,num) {
state.age += num
}
},
getters: {
getAge(state) {
console.log('getAge executes. ');
return state.age
}
},
actions: {
getAge({commit},playload) {
setTimeout(() = >{
commit('getAge',playload)
},1000)}},modules: {
b: {
state: {
c:100
},
mutations: {
changeAge() {
console.log('C in B is updated')}}},d: {
state: {
e:300
},
mutations: {
changeAge() {
console.log(The e in'd 'is updated.)}},modules: {
e: {
state: {
g:500
},
mutations: {}}}}}})Copy the code
When the module name and state name are the same, $store.state.d.e does not obtain the state e of module d, but obtains the state of sub-modules under module d. So,It is recommended that the module name be different from the status name
.
Add a getters property to the D module, and you’ll notice
import Vue from 'vue'
// import Vuex from '.. /vuex/index.js'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
age: 10,},mutations: {
changeAge(state,num) {
state.age += num
}
},
getters: {
getAge(state) {
console.log('getAge executes. ');
return state.age
}
},
actions: {
getAge({commit},playload) {
setTimeout(() = >{
commit('getAge',playload)
},1000)}},modules: {
b: {
state: {
c:100
},
mutations: {
changeAge() {
console.log('C in B is updated')}}},d: {
state: {
e:300
},
// Add new code
getters: {
getD(state) {
return state.e += 50}},mutations: {
changeAge() {
console.log(The e in'd 'is updated.)}},modules: {}}}})Copy the code
// APP.vue
<template>
<div id="app">state.age: {{$store.state.age}} getters: {{$store.state.b.c}} d {{$store.state.d.e}} getD: {{$store.getters.d.getage}} d {{$store.state.d.getage} d {{$store.state.d.getage}<button @click="handle">Click on a + 10</button>
<button @click="$store.commit('changeAge',5)">Mustation method</button>
<button @click="$store.dispatch('changeAge',10)">Method the actions</button>
</div>
</template>
Copy the code
If you go straight through the module. Property to get the value of getters is not available. As we said earlier,Computed attributes put their own attributes on instances
And,computed
和 getters
Is the same, which means,The properties of Getters also mount their own properties directly to the instance
, so,By default
We can go straight throughGetters. Property acquisition
Getters without writing the module name.
In the way I wrote it above, we didn’t even add namespaced namespace attributes, so let’s add Namespaced and see what happens to our code.
import Vue from 'vue'
// import Vuex from '.. /vuex/index.js'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
age: 10,},mutations: {
changeAge(state,num) {
state.age += num
}
},
getters: {
getAge(state) {
console.log('getAge executes. ');
return state.age
}
},
actions: {
getAge({commit},playload) {
setTimeout(() = >{
commit('changeAge',playload)
},1000)}},modules: {
b: {
// Add new code
namespaced: true.state: {
c:100
},
mutations: {
changeAge() {
console.log('C in B is updated')}}},d: {
state: {
e:300
},
getters: {
getD(state) {
return state.e += 50}},mutations: {
changeAge() {
console.log(The e in'd 'is updated.)}},modules: {}}}})Copy the code
Use rules: module name/state or method or getters
// App.vue
<template>
<div id="app">state.age: {{$store.state.age}} getters: {{$store.state.b.c}} d {{$store.state.d.e}} getD: {{$store.getters. GetD}}<button @click="handle">Click on a + 10</button>
<button @click="$store.commit('b/changeAge',5)">Mustation method</button>
<button @click="$store.dispatch('changeAge',10)">Method the actions</button>
</div>
</template>
Copy the code
Instead of implementing all results, the mutations method will only be implemented with the Namespaced: True module, and you’ll notice that when the mutations are added to the moduleNamespaced are true
And then, at this point, it’s going to beThe module's getters, state, mutations, and actions are encapsulated in the middle scope
.
If I add a namespace to a submodule of a submodule, for example, if I add a namespace to a submodule of a submodule of d, do I get values from d/f/changeAge? Let’s test it to see how it works:
import Vue from 'vue'
// import Vuex from '.. /vuex/index.js'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
age: 10,},mutations: {
changeAge(state,num) {
state.age += num
}
},
getters: {
getAge(state) {
console.log('getAge executes. ');
return state.age
}
},
actions: {
getAge({commit},playload) {
setTimeout(() = >{
commit('changeAge',playload)
},1000)}},modules: {
b: {
state: {
c:100
},
mutations: {
changeAge() {
console.log('C in B is updated')}}},d: {
state: {
e:300
},
getters: {
getD(state) {
return state.e += 50}},mutations: {
changeAge() {
console.log(The e in'd 'is updated.)}},modules: {
// Add new code
f: {
namespaced: true.state: {
g:700
},
mutations: {
changeAge() {
console.log('G in f under d is updated')}}}}}}})Copy the code
//App.vue
<template>
<div id="app">state.age: {{$store.state.age}} getters: {{$store.state.b.c}} d {{$store.state.d.e}} getD: {{$store.getters. GetD}}<button @click="handle">Click on a + 10</button>
<button @click="$store.commit('d/f/changeAge',5)">Mustation method</button>
<button @click="$store.dispatch('changeAge',10)">Method the actions</button>
</div>
</template>
Copy the code
The test found the message could not find the method and passedD /f/changeAge cannot be obtained
If not, the current module is used as a scope. If not, it is nested, so you can pass it directlyf/changeAge
Method to obtainmutations
The method of. Test results after modification:
At this time, remove the namepaced of module F and add namespace for module D.
import Vue from 'vue'
// import Vuex from '.. /vuex/index.js'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
age: 10,},mutations: {
changeAge(state,num) {
state.age += num
}
},
getters: {
getAge(state) {
console.log('getAge executes. ');
return state.age
}
},
actions: {
getAge({commit},playload) {
setTimeout(() = >{
commit('changeAge',playload)
},1000)}},modules: {
b: {
state: {
c:100
},
mutations: {
changeAge() {
console.log('C in B is updated')}}},d: {
namespaced: true.state: {
e:300
},
getters: {
getD(state) {
return state.e += 50}},mutations: {
changeAge() {
console.log(The e in'd 'is updated.)}},modules: {
f: {
state: {
g:700
},
mutations: {
changeAge() {
console.log('G in f under d is updated')}}}}}}})Copy the code
//App.vue
<template>
<div id="app">state.age: {{$store.state.age}} getters: {{$store.state.b.c}} d {{$store.state.d.e}} getD: {{$store.getters. GetD}}<button @click="handle">Click on a + 10</button>
<button @click="$store.commit('d/changeAge',5)">Mustation method</button>
<button @click="$store.dispatch('changeAge',10)">Method the actions</button>
</div>
</template>
Copy the code
At this point, throughD/changeAge trigger
, you will find that e, which executes D, is updated, and g, which executes F, which executes D, is also updatedModule has namespaced
When, this time will beThe current module encapsulates a scope
, submodules under d module if notNamespaced attribute
, the remaining submodules all belong to the current declarationNamespaced module management
.
The basic usage of modules is summarized as follows:
Modules, by default, have no scope problems
.The status name should not be the same as the module name
.Properties are computed by default, and can be evaluated directly via getters
.Adding namespaced: True encapsulates the module's properties in this scope
.By default, it checks whether the current module has a Namespaced attribute, and then checks whether the parent has a namespaced attribute. If it does, it counts as a scope, and if it doesn't, the current module is a scope
.
Okay, now that we know the basics of modules, let’s formally write modules by hand
Modules
In the above state, getters, mutations, actions, we only made the root module, and did not take modules into account. Therefore, we cannot nest recursion in the code we wrote before. We will rewrite a Store class, but the previous work is not in vain. With the previous knowledge, we know the internal mechanism, we can easily write a complete Store class, and we can easily understand the internal core of Vuex.
ModuleCollection class
The main purpose of this class is to collect modules, and then format parameter modules into a nested tree of modules for subsequent operations.
Mutations, actions, and state _raw: XXX, // Submodule _children: {a: {_raw: xxx, _children: {} state: xxx.state }, b: { _raw: xxx, _children: {}, state: xxx.state } }, state: xxx.state }Copy the code
Now that you know how to format the arguments passed in by the user into the above tree structure, it’s time to implement the moduleCollection class.
import forEach from "./utils";
export default class ModlueCollection {
constructor(options) {
// register module [] indicates path recursive register module
this.register([],options)
}
register(path,rootModule) {
let newModlue = {
_raw: rootModule,
_children: {},
state: rootModule.state
}
// If the array length is, it is the root module
if(path.length == 0) {
this.root = newModlue
}else {
// Indicates that path has a length such as [a,b]. Store this module in the children property of the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
// Find the parent node step by step
return pre._children[next]
},this.root)
parent._children[path[path.length-1]] = newModlue
}
// Register submodules
if(rootModule.modules) {
forEach(rootModule.modules,(moduleValue, moduleName) = >{
// Recursively form submodules to pass in module names and module values
this.register([...path,moduleName],moduleValue)
})
}
}
}
Copy the code
Through testing, we organized the incoming parameters into a nested tree structure. The above code is still a bit redundant, so let’s extract the code and create a class for each module.
// modules.js
export default class module {
constructor(rootModule) {
this._raw = rootModule,
this._children = {}
this.state = rootModule.state
}
// Find the submodule of the module
getChild(key) {
return this._children[key]
}
// Append submodules to modules
addChild(key, module) {
this._children[key] = module}}Copy the code
// module-collection.js
import forEach from "./utils";
import Module from './module.js'
export default class ModlueCollection {
constructor(options) {
// register module [] indicates path recursive register module
this.register([],options)
}
register(path,rootModule) {
let newModlue = new Module(rootModule)
/ / {
// _raw: rootModule,
// _children: {}
// state: rootModule.state
// }
// If the array length is, it is the root module
if(path.length == 0) {
this.root = newModlue
}else {
// Indicates that path has a length such as [a,b]. Store this module in the children property of the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
// Find the parent node step by step
// return pre._children[next]
return pre.getChild(next)
},this.root)
// parent._children[path[path.length-1]] = newModlue
parent.addChild(path[path.length-1], newModlue)
}
// Register submodules
if(rootModule.modules) {
forEach(rootModule.modules,(moduleValue, moduleName) = >{
// Recursively form submodules to pass in module names and module values
this.register([...path,moduleName],moduleValue)
})
}
}
}
Copy the code
After we remove the code, the effect is still the same, but the idea is clearer. For example, getChild is to find submodules, addChild is to append modules to submodules, and now we add each Module’s method to each Module’s Module. After transforming the parameters passed in by the user into a recursive tree structure, we are back to the Store class we wrote earlier. We need to register the state, mutations, actions and getters methods of all modules on the Store.
Customize the forEachValue method
/ * * * *@param {*} Obj Passed object *@param {*} The callback function deconstructs the value and key */, respectively
const forEachValue = (obj = {}, callback) = > {
Object.keys(obj).forEach(key= > {
// The first argument is the value, and the second argument is the key
callback(obj[key],key)
})
}
export default forEachValue
Copy the code
The purpose of wrapping this method is to make it easy to get the key-value pairs of the object.
installModlue
The installModlue method installs the states and methods in the created tree structure to the Store instance, and then the corresponding data can be obtained through $Store.
// module.js
import forEachValue from "./utils"
export default class module {
constructor(rootModule) {
this._raw = rootModule,
this._children = {}
this.state = rootModule.state
}
// Find the submodule of the module
getChild(key) {
return this._children[key]
}
// Append submodules to modules
addChild(key, module) {
this._children[key] = module
}
// open mutations for the current module
forEachMutations(fn) {
if(this._raw.mutations) {
forEachValue(this._raw.mutations,fn)
}
}
// Iterate through the actions for the current module
forEachActions(fn) {
if(this._raw.actions) {
forEachValue(this._raw.actions,fn)
}
}
// Iterate over the current module's getters
forEachGetters(fn) {
if(this._raw.getters) {
forEachValue(this._raw.getters,fn)
}
}
// Iterate over the child of the current module
forEachChild(fn) {
forEachValue(this._children,fn)
}
}
Copy the code
The purpose of installing all modules is to install the methods and state of the current module into the current Store instance. Previously, we declared a Module class to create each submodule. We can use custom forEachValue in this class to traverse all mutations, actions, getters, and submodules of the current class, using the parameters as the callback of the forEachValue function. It is easy to install in a function passed in to the Store class.
Rewrite the Store class
import applyMinx from './mixin'
import ModuleCollection from './module-collection.js'
let Vue
function installModule(store, rootState, path, module) {
// Collect the status of all modules
if(path.length > 0) { // If it is a submodule, the state of the submodule needs to be defined to the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
return pre[next]
}, rootState)
// Add new attributes by setting properties to reactive
// If the object is not reactive, it will be assigned directly. If the object is reactive, the new attribute will also be reactive
Vue.set(parent,path[path.length -1].module.state)
}
module.forEachMutations((mutations, type) = >{
// Collect all mutations of the module and store them on the store._mutations of the instance
Mutations and Actions of the same name do not override the mutations, so have an array store {changeAge: [fn,fn,fn]}
store._mutations[type] = (store._mutations[type] || [])
store._mutations[type].push((payload) = > {
// Function wrapper parameter passing is flexible
// Make this always refer to the current module state of the instance
mutations.call(store, module.state, payload)
})
})
module.forEachActions((actions, type) = >{
store._actions[type] = (store._actions[type] || [])
store._actions[type].push((payload) = > {
actions.call(store, store, payload)
})
})
module.forEachGetters((getters, key) = >{
// Compute attributes of the same name will be overridden so do not store
store._wrappedGetters[key] = () = > {
return getters(module.state)
}
})
module.forEachChild((child, key) = >{
installModule(store, rootState, path.concat(key), child)
})
}
class Store {
constructor(options) {
this._modules = new ModuleCollection(options)
console.log(this._modules);
this._mutations = Object.create(null) // save the mutation of all modules
this._actions = Object.create(null) // Store actions for all modules
this._wrappedGetters = Object.create(null) // Store getters for all modules
// Install all modules on the Store instance
let state = this._modules.root.state
// this current instance, root state, path, root module
installModule(this, state, [], this._modules.root)
}
// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// This will be reactive
return this._vm._data.$$state
}
commit = (type,playload) = > {
// triggering commit triggers the method in _mutations
this._mutations[type](playload)
}
//
dispatch = (type,playload) = > {
this._actions[type](playload)
}
}
const install = (_Vue) = > {
Vue = _Vue
applyMinx(Vue)
}
export {
Store,
install
}
Copy the code
Now that we have all the methods and states of the tree installed on the Store instance, let’s add a responsive effect to the data.
resetStoreVm
The resetStoreVm method is used to cache getters by placing state on Vue instances.
import applyMinx from './mixin'
import ModuleCollection from './module-collection.js'
import forEachValue from './utils'
let Vue
function installModule(store, rootState, path, module) {
// Collect the status of all modules
if(path.length > 0) { // If it is a submodule, the state of the submodule needs to be defined to the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
return pre[next]
}, rootState)
// Add new attributes by setting properties to reactive
// If the function object is not reactive, it will be assigned directly. If the function object is reactive, the added attribute will also be reactive
Vue.set(parent,path[path.length -1].module.state)
}
module.forEachMutations((mutations, type) = >{
// Collect all mutations of the module and store them on the store._mutations of the instance
Mutations and Actions of the same name do not override the mutations, so have an array store {changeAge: [fn,fn,fn]}
store._mutations[type] = (store._mutations[type] || [])
store._mutations[type].push((payload) = > {
// Function wrapper parameter passing is flexible
// Make this always refer to the current module state of the instance
mutations.call(store, module.state, payload)
})
})
module.forEachActions((actions, type) = >{
store._actions[type] = (store._actions[type] || [])
store._actions[type].push((payload) = > {
actions.call(store, store, payload)
})
})
module.forEachGetters((getters, key) = >{
// Compute attributes of the same name will be overridden so do not store
store._wrappedGetters[key] = () = > {
return getters(module.state)
}
})
module.forEachChild((child, key) = >{
installModule(store, rootState, path.concat(key), child)
})
}
function resetStoreVm (store, state) {
const wrappedGetters = store._wrappedGetters
const computed = {}
store.getters = Object.create(null)
// Cache lazy loading by using VUE's computed
forEachValue(wrappedGetters, (fn, key) = > {
computed[key] = () = > {
return fn()
}
/ / agent
Object.defineProperty(store.getters, key, {
get: () = > store._vm[key]
})
})
// Make the state reactive
store._vm = new Vue({
data() {
return {
$$state: state
}
},
computed
})
}
class Store {
constructor(options) {
this._modules = new ModuleCollection(options)
console.log(this._modules);
this._mutations = Object.create(null) // save the mutation of all modules
this._actions = Object.create(null) // Store actions for all modules
this._wrappedGetters = Object.create(null) // Store getters for all modules
// Register all modules on the Store instance
// this current instance, root state, path, root module
let state = this._modules.root.state
installModule(this, state, [], this._modules.root)
// Implement state responsiveness
resetStoreVm(this,state)
}
// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// This will be reactive
return this._vm._data.$$state
}
commit = (type,payload) = > {
// triggering commit triggers the method in _mutations
this._mutations[type].forEach(fn= > fn(payload))
}
//
dispatch = (type,payload) = > {
this._actions[type].forEach(fn= > fn(payload))
}
}
const install = (_Vue) = > {
Vue = _Vue
applyMinx(Vue)
}
export {
Store,
install
}
Copy the code
After putting the state on the Vue instance, let’s look at the result of the code running:It works exactly as it did in the beginning, with modules nesting implemented, but not yetNamespaced namespace
The function. Here’s how to improve modulesnamespaced
The module.
namespaced
import forEachValue from "./utils";
import Module from './module.js'
export default class ModlueCollection {
constructor(options) {
// register module [] indicates path recursive register module
this.register([],options)
}
register(path,rootModule) {
let newModlue = new Module(rootModule)
/ / {
// _raw: rootModule,
// _children: {}
// state: rootModule.state
// }
// If the array length is, it is the root module
if(path.length == 0) {
this.root = newModlue
}else {
// Indicates that path has a length such as [a,b]. Store this module in the children property of the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
// Find the parent node step by step
// return pre._children[next]
return pre.getChild(next)
},this.root)
// parent._children[path[path.length-1]] = newModlue
parent.addChild(path[path.length-1], newModlue)
}
// Register submodules
if(rootModule.modules) {
forEachValue(rootModule.modules,(moduleValue, moduleName) = >{
// Recursively form submodules to pass in module names and module values
this.register([...path,moduleName],moduleValue)
})
}
}
// Add new code
getNamespaced(path) { // Get the namespace
let root = this.root
return path.reduce((pre, next) = >{
//[b,c]
// Get the submodule to see if it has the Namespaced attribute
root = root.getChild(next)
// If you have a namespace attribute, add it to the namespace
return pre + (root.namespaced ? next + '/' :' ')},' ')}}Copy the code
function installModule(store, rootState, path, module) {
// Add code to get namespace
let namespaced = store._modules.getNamespaced(path)
console.log(namespaced);
// Collect the status of all modules
if(path.length > 0) { // If it is a submodule, the state of the submodule needs to be defined to the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
return pre[next]
}, rootState)
// Add new attributes by setting properties to reactive
// If the function object is not reactive, it will be assigned directly. If the function object is reactive, the added attribute will also be reactive
Vue.set(parent,path[path.length -1].module.state)
}
module.forEachMutations((mutations, type) = >{
// Collect all mutations of the module and store them on the store._mutations of the instance
Mutations and Actions of the same name do not override the mutations, so have an array store {changeAge: [fn,fn,fn]}
store._mutations[namespaced + type] = (store._mutations[namespaced + type] || [])
store._mutations[namespaced + type].push((payload) = > {
// Function wrapper parameter passing is flexible
// Make this always refer to the current module state of the instance
mutations.call(store, module.state, payload)
})
})
module.forEachActions((actions, type) = >{
store._actions[namespaced + type] = (store._actions[namespaced + type] || [])
store._actions[namespaced + type].push((payload) = > {
actions.call(store, store, payload)
})
})
module.forEachGetters((getters, key) = >{
// Compute attributes of the same name will be overridden so do not store
store._wrappedGetters[key] = () = > {
return getters(module.state)
}
})
module.forEachChild((child, key) = >{
installModule(store, rootState, path.concat(key), child)
})
}
Copy the code
import forEachValue from "./utils"
export default class module {
constructor(rootModule) {
this._raw = rootModule,
this._children = {}
this.state = rootModule.state
}
// Add code attribute accessors
get namespaced () {
return this._raw.namespaced
}
// Find the submodule of the module
getChild(key) {
return this._children[key]
}
// Append submodules to modules
addChild(key, module) {
this._children[key] = module
}
// open mutations for the current module
forEachMutations(fn) {
if(this._raw.mutations) {
forEachValue(this._raw.mutations,fn)
}
}
// Iterate through the actions for the current module
forEachActions(fn) {
if(this._raw.actions) {
forEachValue(this._raw.actions,fn)
}
}
// Iterate over the current module's getters
forEachGetters(fn) {
if(this._raw.getters) {
forEachValue(this._raw.getters,fn)
}
}
// Iterate over the child of the current module
forEachChild(fn) {
forEachValue(this._children,fn)
}
}
Copy the code
When you implement Namespaced, you’re going to iterate over whether or not each module has one when it’s installedNamespaced attribute
, and then join the paths together.
registerModule
Vuex and VuE-Router support dynamic addition of modules and routes, so we need to add a registerModule method to store to dynamically add modules.
// module-collection.js
import forEachValue from "./utils";
import Module from './module.js'
export default class ModlueCollection {
constructor(options) {
// register module [] indicates path recursive register module
this.register([],options)
}
register(path,rootModule) {
let newModlue = new Module(rootModule)
/ / {
// _raw: rootModule,
// _children: {}
// state: rootModule.state
// }
// If the array length is, it is the root module
// Map the current module instance to dynamically add modules
rootModule.rawModule = newModlue
if(path.length == 0) {
this.root = newModlue
}else {
// Indicates that path has a length such as [a,b]. Store this module in the children property of the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
// Find the parent node step by step
// return pre._children[next]
return pre.getChild(next)
},this.root)
// parent._children[path[path.length-1]] = newModlue
parent.addChild(path[path.length-1], newModlue)
}
// Register submodules
if(rootModule.modules) {
forEachValue(rootModule.modules,(moduleValue, moduleName) = >{
// Recursively form submodules to pass in module names and module values
this.register([...path,moduleName],moduleValue)
})
}
}
...
}
Copy the code
Added rootModule.rawModule = newModlue Cause: After the new Modlue module, we added an attribute mapping for the current module instance to ourselves. This is in order to dynamically register the module, we need to install the registered module on the Store instance. The installation requires the methods of traverse mutations, actions, and getters in the Module instance, but there is no Module instance in the added Module, so you need to add an attribute for the new Module when registering the Module, which maps to the current Module instance.
// store.js
import applyMinx from './mixin'
import ModuleCollection from './module-collection.js'
import forEachValue from './utils'
let Vue
function installModule(store, rootState, path, module) {
// Get the namespace
let namespaced = store._modules.getNamespaced(path)
console.log(namespaced);
// Collect the status of all modules
if(path.length > 0) { // If it is a submodule, the state of the submodule needs to be defined to the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
return pre[next]
}, rootState)
// Add new attributes by setting properties to reactive
// If the object is not reactive, it will be assigned directly. If the object is reactive, the new attribute will also be reactive
Vue.set(parent,path[path.length -1].module.state)
}
module.forEachMutations((mutations, type) = >{
// Collect all mutations of the module and store them on the store._mutations of the instance
Mutations and Actions of the same name do not override the mutations, so have an array store {changeAge: [fn,fn,fn]}
store._mutations[namespaced + type] = (store._mutations[namespaced + type] || [])
store._mutations[namespaced + type].push((payload) = > {
// Function wrapper parameter passing is flexible
// Make this always refer to the current module state of the instance
mutations.call(store, module.state, payload)
})
})
module.forEachActions((actions, type) = >{
store._actions[namespaced + type] = (store._actions[namespaced + type] || [])
store._actions[namespaced + type].push((payload) = > {
actions.call(store, store, payload)
})
})
module.forEachGetters((getters, key) = >{
// Compute attributes of the same name will be overridden so do not store
store._wrappedGetters[key] = () = > {
return getters(module.state)
}
})
module.forEachChild((child, key) = >{
installModule(store, rootState, path.concat(key), child)
})
}
function resetStoreVm (store, state) {
// Store the last instance
let oldVm = state._vm
const wrappedGetters = store._wrappedGetters
const computed = {}
store.getters = Object.create(null)
forEachValue(wrappedGetters, (fn, key) = > {
computed[key] = () = > {
return fn()
}
/ / agent
Object.defineProperty(store.getters, key, {
get: () = > store._vm[key]
})
})
// Make the state reactive
store._vm = new Vue({
data() {
return {
$$state: state
}
},
computed
})
// Determine if the last vue instance exists and if so, destroy the instance during the next DOM update
if(oldVm) {
Vue.nextTick(() = > oldVm.$destoryed())
}
}
class Store {
constructor(options) {
this._modules = new ModuleCollection(options)
console.log(this._modules);
this._mutations = Object.create(null) // save the mutation of all modules
this._actions = Object.create(null) // Store actions for all modules
this._wrappedGetters = Object.create(null) // Store getters for all modules
// Register all modules on the Store instance
// this current instance, root state, path, root module
let state = this._modules.root.state
installModule(this, state, [], this._modules.root)
// console.log(this._module);
// Implement state responsiveness
resetStoreVm(this,state)
}
// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// This will be reactive
return this._vm._data.$$state
}
commit = (type,payload) = > {
// triggering commit triggers the method in _mutations
this._mutations[type].forEach(fn= > fn(payload))
}
//
dispatch = (type,payload) = > {
this._actions[type].forEach(fn= > fn(payload))
}
// Add new code
registerModule (path,rawModule) {
// Encapsulate an array
if(!Array.isArray(path)) path = [path]
// Register module
this._modules.register(path,rawModule)
// Install the module to register the module using the method on the module instance to make a module map where the module is collected
installModule(this.this.state,path,rawModule.rawModule)
/ / reset getters
resetStoreVm(this.this.state)
}
}
const install = (_Vue) = > {
Vue = _Vue
applyMinx(Vue)
}
export {
Store,
install
}
Copy the code
After the module is installed, mutations, actions and state of the module can be used normally as other modules, but the method of getters cannot be used at present, because we process getters in the resetStoreVm method. So we need to execute the resetStoreVm function once more before we can use getters to add modules. However, we used the resetStoreVm method to create an instance of vUE when we set the module state to be responsive, and we used the resetStoreVm method to create an instance of vue when we added the module, so we have two instances of vUE. We need the nextTick method to execute the previous instance’s $deStoryed () method on the next DOM update to destroy the previous vue instance. Test results:
// store/store.js. store.registerModule(['h'] and {namespaced:true.state: {
l: 9
},
getters: {
getL(state) {
return state.l
}
}
})
...
Copy the code
The test found that the root module dynamically added an H module. The dynamic registration module is completed.
Persistent plugins state
Vuex also supports the use of plug-ins internally, some of which can be registered when creating a Store.
import Vue from 'vue'
// import Vuex from '.. /vuex/index.js'
import Vuex from 'vuex'
Vue.use(Vuex)
function persists(store) {}const store = new Vuex.Store({
plugins: [
persists
],
state: {
age: 10,}})export default store
Copy the code
When a plug-in is executed, a Store argument is provided internally, which is a Store instance, so that the desired plug-in can be written from the instance. So now the custom vuex will also function as a plug-in.
class Store{
constructor(options){...// Store plugins
this._subscriber = []
// Iterate over the plug-in and execute
options.plugins.forEach(fn= > fn(this))... }// Publish a subscription
subscriber(fn) {
// Automatically triggered when the state changes
this._subscriber.push(fn)
}
// Update the status
replaceState(newState) {
this._vm._data.$$state = newState
}
....
// omit the following code
}
Copy the code
import Vue from 'vue'
import Vuex from '.. /vuex/index.js'
// import Vuex from 'vuex'
Vue.use(Vuex)
function persists(store) {
let local = localStorage.getItem('VUEX')
if (local) {
// Each page refresh gets the latest value from localStorage and displays it on the page
store.replaceState(JSON.parse(local))
}
store.subscriber((mutations,state) = >{
// Store the state in localStorage
localStorage.setItem('VUEX'.JSON.stringify(state))
})
}
const store = new Vuex.Store({
plugins: [
persists
],
state: {
age: 10,},...// omit the following code
})
export default store
Copy the code
// store.js
import applyMinx from './mixin'
import ModuleCollection from './module-collection.js'
import forEachValue from './utils'
let Vue
// Add code to get the latest value
function getState(store, path) {
return path.reduce((pre, next) = > {
return pre[next]
},store.state)
}
function installModule(store, rootState, path, module) {
// Get the namespace
let namespaced = store._modules.getNamespaced(path)
// Collect the status of all modules
if(path.length > 0) { // If it is a submodule, the state of the submodule needs to be defined to the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
return pre[next]
}, rootState)
// Add new attributes by setting properties to reactive
// If the function object is not reactive, it will be assigned directly. If the function object is reactive, the added attribute will also be reactive
Vue.set(parent,path[path.length -1].module.state)
}
module.forEachMutations((mutations, type) = >{
// Collect all mutations of the module and store them on the store._mutations of the instance
Mutations and Actions of the same name do not override the mutations, so have an array store {changeAge: [fn,fn,fn]}
store._mutations[namespaced + type] = (store._mutations[namespaced + type] || [])
store._mutations[namespaced + type].push((payload) = > {
// Function wrapper parameter passing is flexible
// Make this always refer to the current module state of the instance
Module. state may be the old value if it is used all the time, so you need to get the latest value
mutations.call(store, getState(store, path), payload) // State change triggers subscriber
// Add new code
The first argument is an object that records the names of mutations and the trigger COMMIT. The second argument is the latest status value
store._subscriber.forEach(sub= > sub({mutations,type},store.state))
})
})
module.forEachActions((actions, type) = >{
store._actions[namespaced + type] = (store._actions[namespaced + type] || [])
store._actions[namespaced + type].push((payload) = > {
actions.call(store, store, payload)
})
})
module.forEachGetters((getters, key) = >{
// Compute attributes of the same name will be overridden so do not store
store._wrappedGetters[key] = () = > {
return getters(getState(store, path))
}
})
module.forEachChild((child, key) = >{
installModule(store, rootState, path.concat(key), child)
})
}
Copy the code
We implemented a persistent state of our own through a plug-in above, where each page refresh reads the latest value in localStorage.
Distinguish between actions and mutations
In VUEX, only changes in status are allowed through mutations in strict mode, while changes in status in other cases are not allowed and errors will be reported. Here, to implement this function:
import applyMinx from './mixin'
import ModuleCollection from './module-collection.js'
import forEachValue from './utils'
let Vue
// Get the latest value
function getState(store, path) {
return path.reduce((pre, next) = > {
return pre[next]
},store.state)
}
function installModule(store, rootState, path, module) {
// Get the namespace
let namespaced = store._modules.getNamespaced(path)
console.log(namespaced);
// Collect the status of all modules
if(path.length > 0) { // If it is a submodule, the state of the submodule needs to be defined to the root module
let parent = path.slice(0, -1).reduce((pre, next) = > {
return pre[next]
}, rootState)
// Add new attributes by setting properties to reactive
// If the function object is not reactive, it will be assigned directly. If the function object is reactive, the added attribute will also be reactive
store._WithCommitting(() = >{
Vue.set(parent,path[path.length -1].module.state)
})
}
module.forEachMutations((mutations, type) = >{
// Collect all mutations of the module and store them on the store._mutations of the instance
Mutations and Actions of the same name do not override the mutations, so have an array store {changeAge: [fn,fn,fn]}
store._mutations[namespaced + type] = (store._mutations[namespaced + type] || [])
store._mutations[namespaced + type].push((payload) = > {
// Function wrapper parameter passing is flexible
// Make this always refer to the current module state of the instance
// Implement the plugin, where the value of state needs to be updated and the state may be replaced internally
store._WithCommitting(() = >{
mutations.call(store, getState(store, path), payload) // State change triggers subscriber
})
The first argument is an object that records the names of mutations and the trigger COMMIT. The second argument is the latest status value
store._subscriber.forEach(sub= > sub({mutations,type},store.state))
})
})
module.forEachActions((actions, type) = >{
store._actions[namespaced + type] = (store._actions[namespaced + type] || [])
store._actions[namespaced + type].push((payload) = > {
actions.call(store, store, payload)
})
})
module.forEachGetters((getters, key) = >{
// Compute attributes of the same name will be overridden so do not store
store._wrappedGetters[key] = () = > {
return getters(getState(store, path))
}
})
module.forEachChild((child, key) = >{
installModule(store, rootState, path.concat(key), child)
})
}
function resetStoreVm (store, state) {
let oldVm = state._vm
const wrappedGetters = store._wrappedGetters
const computed = {}
store.getters = Object.create(null)
forEachValue(wrappedGetters, (fn, key) = > {
computed[key] = () = > {
return fn()
}
/ / agent
Object.defineProperty(store.getters, key, {
get: () = > store._vm[key]
})
})
// Make the state reactive
store._vm = new Vue({
data() {
return {
$$state: state
}
},
computed
})
if(oldVm) {
Vue.nextTick(() = > oldVm.$destoryed())
}
// Enable strict mode
if (store.strict) {
store._vm.$watch(() = >store._vm._data.$$state,() = >{
console.assert(store._committing,'Status can only be changed through mutations')
},{deep:true.sync:true}) // Synchronization is performed as soon as the state changes}}class Store {
constructor(options) {
this._modules = new ModuleCollection(options)
console.log(this._modules);
this._mutations = Object.create(null) // save the mutation of all modules
this._actions = Object.create(null) // Store actions for all modules
this._wrappedGetters = Object.create(null) // Store getters for all modules
// Register all modules on the Store instance
// this current instance, root state, path, root module
let state = this._modules.root.state
installModule(this, state, [], this._modules.root)
// console.log(this._module);
// Implement state responsiveness
resetStoreVm(this,state)
this.strict = options.strict
/ / the plugin
this._subscriber = []
// Iterate over the plug-in and execute
options.plugins.forEach(fn= > fn(this))
/ / synchronize Watcher
this._committing = false
}
// Class property accessor This method is triggered relative to the proxy when the user obtains the instance property state from the instance
get state() {
$$state = this._vm._data.$$state = this._vm._data.$$state = new vue
// This will be reactive
return this._vm._data.$$state
}
Watcher can only change the status via mutations
_WithCommitting(fn) {
let committing = this._committing
this._committing = true "// Slice () label _case (true) before function call ()
fn()
this._committing = committing
}
commit = (type,payload) = > {
// triggering commit triggers the method in _mutations
this._mutations[type].forEach(fn= > fn(payload))
}
//
dispatch = (type,payload) = > {
this._actions[type].forEach(fn= > fn(payload))
}
registerModule (path,rawModule) {
// Encapsulate an array
if(!Array.isArray(path)) path = [path]
// Register module
this._modules.register(path,rawModule)
// Install the module to register the module using the method on the module instance to make a module map where the module is collected
installModule(this.this.state,path,rawModule.rawModule)
/ / reset getters
resetStoreVm(this.this.state)
}
// Publish a subscription
subscriber(fn) {
// Automatically triggered when the state changes
this._subscriber.push(fn)
}
// Update the status
replaceState(newState) {
this._WithCommitting(() = >{
this._vm._data.$$state = newState
})
}
}
const install = (_Vue) = > {
Vue = _Vue
applyMinx(Vue)
}
export {
Store,
install
}
Copy the code
In strict mode, you need to set a _research identifier inside the Store (case), change the _research identifier to true (case) if mutations change the status (case), and set the status of all other changes to false (case). “This uses the _withresearch function (right) to label (change) _case (change), inside resetStoreVm decide whether to turn on strict mode (right), use $watch synchronization depth to listen for state changes. Determine whether the user has changed the status via mutations, based on the _research flag set up.
Helper function helper
The main part of vuex source code above has been completed, the following write vuex in the four auxiliary functions to achieve the principle.
//App.vue
<template>
<div id="app">State.age: {{age}} getters: {{$store.getters. GetAge}} b module c {{$store.state.b.c}} D module E {{$store.state.d.e}}<button @click="$store.state.age+=10"></button>
<button @click="$store.commit('changeAge',5)">Mustation method</button>
<button @click="$store.dispatch('changeAge',10)">Method the actions</button>
</div>
</template>
<script>
const mapState = (arrayList) = > {
const obj = {}
for(let i = 0; i < arrayList.length; i++) {
obj[arrayList[i]] = function() {
console.log(this);
return this.$store.state[arrayList[i]]
}
}
return obj
}
export default {
name: 'App'.computed: {
...mapState(['age']),
// age(){
// return this.$store.state.age
// }}}</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Copy the code
The use of... MapState (['age']) {return this.$store.state.age}; MapGetters ([getAge]) = getAge(){return this.$store.getage}Copy the code
We know that we pass in an array and return an object, so the code looks like this:
const mapState = (arrayList) = > {
const obj = {}
for(let i = 0; i < arrayList.length; i++) {
obj[arrayList[i]] = function() {
console.log(this);
return this.$store.state[arrayList[i]]
}
}
return obj
}
Copy the code
The result is as follows: as before, the remaining three auxiliary functions can be written in this way.The code for the auxiliary function is as follows:
// helper.js
const mapState = (arrayList) = > {
const obj = {}
for (let i = 0; i < arrayList.length; i++) {
obj[arrayList[i]] = function () {
console.log(this);
return this.$store.state[arrayList[i]]
}
}
return obj
}
const mapGetters = (arrayList) = > {
const obj = {}
for (let i = 0; i < arrayList.length; i++) {
obj[arrayList[i]] = function () {
return this.$store.getters.getAge
}
}
return obj
}
const mapMutations = (arrayList) = > {
const obj = {}
for (let i = 0; i < arrayList.length; i++) {
obj[arrayList[i]] = function (payload) {
return this.$store.commit(arrayList[i], payload)
}
}
return obj
}
const mapActions = (arrayList) = > {
const obj = {}
for (let i = 0; i < arrayList.length; i++) {
obj[arrayList[i]] = function (payload) {
return this.$store.dispatch(arrayList[i], payload)
}
}
return obj
}
export {
mapState,
mapGetters,
mapActions,
mapMutations
}
Copy the code
// App.vue
<template>
<div id="app">State.age: {{age}} getters: {{getAge}} b module c {{$store.state.b.c}} D module e {{$store.state.d.e}<button @click="changeAge(5)">Mustation method</button>
<button @click="$store.dispatch('changeAge',10)">Method the actions</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations} from './vuex/index.js'
export default {
name: 'App'.computed: {
...mapState(['age']),
...mapGetters(['getAge'])},methods: {
...mapMutations(['changeAge']),}}</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Copy the code
The test results are as follows:
My personal understanding of Vuex
Vuex is a state management mode, through which we can communicate between components, because the install method uses vue’s mixin method to inject Store instances into each component, In the resetStoreVm method, the reactive principle of VUE itself is used to realize state responsiveness, and the cache effect of Getters is realized through computed. Then, the mutations and actions methods defined by users are stored through the idea of publishing and subscription. When the users trigger commit and dispatch, they can subscribe mutations and Actions to find out the corresponding methods. In the case of module nesting, vuex internally formats all modules into a tree using a moduleCollection class, installs the formatted tree onto the Store instance using the installModule method, and finally, The state of all modules is set to reactive by using the principle of vUE internal reactive through resetStoreVm method. Vuex is responsive and also supports the use of plug-ins. There are subscribe methods and replaceState methods in Store, through which vuex can realize the persistence of state. The above shows that VUex is highly dependent on vUE responsive and plug-in system. This is my personal understanding of VUex.
If you do not understand the Vue responsive principle, you can go to the previous article to read the Vue responsive principle
To sum up, the main part of vuex source code has been basically all over the top. The core of VUex has been basically realized. I hope it will be helpful for you to explore the core mechanism of VUex. If you have any mistakes, please correct them in the comments section. The relevant source code has been uploaded to Gitee, interested partners can go to get the source code stamp I get the source code !!!!! .