Handwriting Vuex core principle
@[toc]
I. Core principles
- Vuex is essentially an object
- The Vuex object has two properties, the install method and the Store class
- The install method is used to mount the store instance to all components. Note that it is the same Store instance.
- The Store class has methods such as Commit and Dispatch. The Store class wraps the state passed in by the user into data as an argument to new Vue, thus implementing a response to the state value.
2. Basic preparations
Let’s first build a project using vue-CLI
Delete some unnecessary builds after the project directory is temporarily as follows:
Have put the project on Github: github.com/Sunny-lucki… Can you humble yourself and ask for a star. Do you have any questions or suggestions
We mainly look at the App. Vue, the main js, store/index. Js
The code is as follows:
App.vue
<template>
<div id="app">
123
</div>
</template>
Copy the code
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},mutations: {},actions: {},modules: {}})Copy the code
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h= > h(App)
}).$mount('#app')
Copy the code
Now let’s start the project. See if the project initialization was successful.
Ok, nothing wrong, initialization succeeded.
Now we’ve decided to create our own Vuex, so create the myvuex.js file
The current directory is as follows
Let’s introduce Vuex and change it to our myVuex
//store/index.js
import Vue from 'vue'
import Vuex from './myVuex' // Modify the code
Vue.use(Vuex)
export default new Vuex.Store({
state: {},mutations: {},actions: {},modules: {}})Copy the code
Third, analyze the essence of Vuex
Let’s start by asking how Vuex was introduced into the Vue project.
- Install Vuex, then pass
import Vuex from 'vuex'
The introduction of - Var store = new vuex.store ({… New Vue({store});
- Vue.use(Vuex) enables each component to have a Store instance
What do we learn from this introduction process?
- We get a store instance by new vuex.store ({}), that is, we introduce Vuex with the store class as a property of the Vuex object. Because what you import is essentially a reference to an exported object.
So we can make a preliminary assumption
Class Store{
}
let Vuex = {
Store
}
Copy the code
- We also use vue.use (), and one of the principles of vue.use is to execute the install method on the object
So, we can go one step further and assume that Vuex has the install method.
Class Store{
}
let install = function(){}let Vuex = {
Store,
install
}
Copy the code
At this point, can you write down Vuex roughly?
Simply export the above Vuex object, which is myvuex.js
//myVuex.js
class Store{}let install = function(){}let Vuex = {
Store,
install
}
export default Vuex
Copy the code
Let’s execute the project, and if there are no errors, our assumptions are correct.
Oh, my God. It was right. No problem!
Iv. Analyze vue.use
Vue.use(plugin);
(1) Parameters
{ Object | Function } plugin
Copy the code
(2) Usage
Install the vue.js plug-in. If the plug-in is an object, you must provide the install method. If the plug-in is a function, it is treated as the install method. When the install method is called, Vue is passed in as an argument. When the install method is called multiple times by the same plug-in, the plug-in is installed only once.
For more information on how to develop Vue plug-ins, check out this article, which is very simple and takes less than two minutes to read: How to develop Vue plug-ins?
(3) Function
To register the plug-in, simply call the install method and pass Vue as a parameter. But in detail there are two parts of logic to deal with:
1. The type of the plug-in, which can be either the install method or an object containing the install method.
2. The plug-in can be installed only once. Ensure that there are no duplicate plug-ins in the plug-in list.
(4) Implementation
Vue.use = function(plugin){
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if(installedPlugins.indexOf(plugin)>-1) {return this; } <! -- Other parameters -->const args = toArray(arguments.1);
args.unshift(this);
if(typeof plugin.install === 'function'){
plugin.install.apply(plugin,args);
}else if(typeof plugin === 'function'){
plugin.apply(null,plugin,args);
}
installedPlugins.push(plugin);
return this;
}
Copy the code
1. Add the use method to vue.js and accept a parameter plugin.
2. First check whether the plugin has been registered. If it has been registered, the method execution will be terminated directly.
The toArray method is used to convert a class-like array into a real array. Use the toArray method to get arguments. All arguments except the first assign the resulting list to Args, and then add Vue to the top of args’ list. The purpose of this is to ensure that the first parameter of the install method is Vue and the rest of the parameters are passed when the plug-in is registered.
4. Since the plugin parameter supports objects and function types, you can know which way the user uses the plug-in by determining which function is plugin.install or plugin, and then execute the plug-in written by the user and pass args as the parameter.
Finally, add the plugin to installedPlugins to ensure that the same plugin is not registered over and over again. ~~ reminds me of the time when an interviewer asked me why plug-ins wouldn’t be reloaded!! Crying, now I understand.)
Five, improve the install method
We mentioned earlier that vue.use (Vuex) allows each component to have a Store instance.
What does that mean??
To see mian. Js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false;
new Vue({
store,
render: h= > h(App)
}).$mount('#app');
Copy the code
As you can see, we are only using the Store, the store instance exported by store/index.js, as part of the Vue parameter.
The problem is that Vue is the root component. This means that only the root component has the store value, and no other component has it yet, so we need to make the other components have the store as well.
Therefore, the install method can be perfected this way
Let install = function(Vue){vue.mixin ({beforeCreate(){if (this.$options && this.$options. Store){// if it is the root component This.$store = this.$parent && this.$parent.$store}})}Copy the code
Explain the code:
- The Vue parameter is passed as the parameter when we analyze Vue. Use in section 4, and then when we execute install.
- Mixins are used to mix the contents of mixins into Vue’s initial parameters, options. Those of you who use Vue have already used mixins.
- Why beforeCreate rather than created? Because if you’re doing a created operation,
$options
It’s already initialized. - If we determine that the current component is the root component, we will hang the store we passed in on the root component instance with the property name
$store
. - If we determine that the current component is a child component, we will put our root component
$store
Also copy to child components. Pay attention to isReplication of references, so each component has the same$store
Mount it on its body.
If the current component is a child, why can we get $store directly from the parent? This reminds me of a question I was once asked by an interviewer: the order in which the parent and child components are executed?
A: Parent beforeCreate-> Parent created -> parent beforeMounte -> child beforeCreate-> child Create-> child beforeMount -> Child Mounted -> parent
BeforeCreate = beforeCreate = beforeCreate = beforeCreate = beforeCreate = beforeCreate = beforeCreate = beforeCreate = beforeCreate
Vi. Implement state of Vuex
<p>{{this.$store.state.num}}</p>
Copy the code
We all know that we can get the value of state from this statement but we haven’t implemented it in the Store class yet, so obviously, we’re going to get an error right now.
As I said, this is how we use Store
export default new Vuex.Store({
state: {
num:0
},
mutations: {},actions: {},modules: {}})Copy the code
In other words, let’s take this object
{
state: {
num:0
},
mutations: {
},
actions: {
},
modules: {
}
}
Copy the code
As a parameter.
So we can get this object directly from the Class Store
class Store{
constructor(options){
this.state = options.state || {}
}
}
Copy the code
So can you use it directly?
Try it!
//App.vue
<template>
<div id="app">
123
<p>{{this.$store.state.num}}</p>
</div>
</template>
Copy the code
Running result:
Isn’t that awesome? How could it be so easy… I can’t believe it.
Oh no, of course it’s not that simple, we’re missing the point that the values in state are also responsive, so we’re not implementing a responsive.
Once an interviewer asked me what the difference is between Vuex and global variables. So that’s the point of noticing the difference
So how do you make it responsive? We know that when we pass in new Vue (), the data we pass in is reactive, so why not pass in a new Vue and pass in state as data? No, that’s it.
class Store{
constructor(options) {
this.vm = new Vue({
data: {state:options.state
}
})
}
}
Copy the code
Now it’s time to implement the response, but how do we get the state? This.$store.vm. State? But it is different from what we usually use, so it needs to be transformed.
We can add a state property to the Store class. This property automatically triggers the GET interface.
class Store{
constructor(options) {
this.vm = new Vue({
data: {state:options.state
}
})
}
// Add code
get state() {return this.vm.state
}
}
Copy the code
This is ES6 syntax, kind of like the get interface of object.defineProperty
Successful implementation.
Implement getters
//myVuex.js
class Store{
constructor(options) {
this.vm = new Vue({
data: {state:options.state
}
})
// Add code
let getters = options.getter || {}
this.getters = {}
Object.keys(getters).forEach(getterName= >{
Object.defineProperty(this.getters,getterName,{
get:() = >{
return getters[getterName](this.state)
}
})
})
}
get state() {return this.vm.state
}
}
Copy the code
We save the getter that the user passed in to our getters array.
The most interesting thing is that there are often interview questions asking why you don’t use parentheses when you use getters. If I hadn’t learned the handwriting Vuex, I wouldn’t have been puzzled. So this question is like asking why we don’t use parentheses when we write a variable. (e.g. {{num}} instead of {{num()}}))
Object. DefineProperty get interface
Ok, so let’s see if we can use the getter.
//store/index.js
import Vue from 'vue'
import Vuex from './myVuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num:0
},
// Add test code
getter: {getNum:(state) = >{
return state.num
}
},
mutations: {},actions: {},})Copy the code
<template>
<div id="app">
123
<p>State: {{this. $store. State. The num}}</p>
<p>getter:{{this.$store.getters.getNum}}</p>
</div>
</template>
Copy the code
Perfect. No accidents.
Viii. Implement mutation
//myVuex.js
class Store{
constructor(options) {
this.vm = new Vue({
data: {state:options.state
}
})
let getters = options.getter || {}
this.getters = {}
Object.keys(getters).forEach(getterName= >{
Object.defineProperty(this.getters,getterName,{
get:() = >{
return getters[getterName](this.state)
}
})
})
// Add code
let mutations = options.mutations || {}
this.mutations = {}
Object.keys(mutations).forEach(mutationName= >{
this.mutations[mutationName] = (arg) = > {
mutations[mutationName](this.state,arg)
}
})
}
get state() {return this.vm.state
}
}
Copy the code
Mutations are the same as getters, they’re storing the mutations that come in from the user using the mutations object.
But how? Recall how we triggered mutations.
this.$store.commit('incre',1)
Copy the code
Yes, in this form. You can see that the store object has the commit method. The COMMIT method triggers some corresponding method on the mutations object, so we can add the COMMIT method to the Store class
//myVuex.js
class Store{
constructor(options) {
this.vm = new Vue({
data: {state:options.state
}
})
let getters = options.getter || {}
this.getters = {}
Object.keys(getters).forEach(getterName= >{
Object.defineProperty(this.getters,getterName,{
get:() = >{
return getters[getterName](this.state)
}
})
})
let mutations = options.mutations || {}
this.mutations = {}
Object.keys(mutations).forEach(mutationName= >{
this.mutations[mutationName] = (arg) = > {
mutations[mutationName](this.state,arg)
}
})
}
// Add code
commit(method,arg){
this.mutations[method](arg)
}
get state() {return this.vm.state
}
}
Copy the code
All right, so let’s test that out.
<template>
<div id="app">
123
<p>state:{{this.$store.state.num}}</p>
<p>getter:{{this.$store.getters.getNum}}</p>
<button @click="add">+ 1</button>
</div>
</template>
// Add test code
<script>
export default {
methods: {add(){
this.$store.commit('incre'.1)}}}</script>
Copy the code
store/index.js
//store/index.js
import Vue from 'vue'
import Vuex from './myVuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num:0
},
getter: {getNum:(state) = >{
return state.num
}
},
// Add test code
mutations: {
incre(state,arg){
state.num += arg
}
},
actions: {},})Copy the code
The operation succeeds.
Implement actions
When they achieve mutations, the implementation of actions is very simple, very similar, if you look at the code:
//myVuex.js
class Store{
constructor(options) {
this.vm = new Vue({
data: {state:options.state
}
})
let getters = options.getter || {}
this.getters = {}
Object.keys(getters).forEach(getterName= >{
Object.defineProperty(this.getters,getterName,{
get:() = >{
return getters[getterName](this.state)
}
})
})
let mutations = options.mutations || {}
this.mutations = {}
Object.keys(mutations).forEach(mutationName= >{
this.mutations[mutationName] = (arg) = > {
mutations[mutationName](this.state,arg)
}
})
// Add code
let actions = options.actions
this.actions = {}
Object.keys(actions).forEach(actionName= >{
this.actions[actionName] = (arg) = >{
actions[actionName](this,arg)
}
})
}
// Add code
dispatch(method,arg){
this.actions[method](arg)
}
commit(method,arg){
console.log(this);
this.mutations[method](arg)
}
get state() {return this.vm.state
}
}
Copy the code
It’s the same, but there’s one thing that needs to be explained, which is why I’m passing this in here. This represents the Store instance itselfThis is because we use actions like this:
actions: {
asyncIncre({commit},arg){
setTimeout(() = >{
commit('incre',arg)
},1000)}},Copy the code
{commit} is the deconstruction of this, the store instance 。
So let’s test that out.
<template>
<div id="app">
123
<p>state:{{this.$store.state.num}}</p>
<p>getter:{{this.$store.getters.getNum}}</p>
<button @click="add">+ 1</button>
<button @click="asyncAdd">Asynchronous + 2</button>
</div>
</template>
<script>
export default {
methods: {add(){
this.$store.commit('incre'.1)},asyncAdd(){
this.$store.dispatch('asyncIncre'.2)}}}</script>
Copy the code
store/index.js
//store/index.js
import Vue from 'vue'
import Vuex from './myVuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num:0
},
getter: {getNum:(state) = >{
return state.num
}
},
mutations: {
incre(state,arg){
state.num += arg
}
},
// Add test code
actions: {
asyncIncre({commit},arg){
setTimeout(() = >{
commit('incre',arg)
},1000)}}})Copy the code
Oh my God, there’s an error, and what it says here is that when I get here I find that this is undefined.
However, no, we also implemented here when the mutation was implemented, and the implementation succeeded ah.
Let’s break it down:
this.$store.commit('incre'.1)
Copy the code
So when we execute this code, when we commit, we’re referring to whoever called this, so this points to $store.
this.$store.dispatch('asyncIncre'.2)
Copy the code
Execute this code, and it will execute
asyncIncre({commit},arg){
setTimeout(() = >{
commit('incre',arg)
},1000)}Copy the code
Do you see the problem? Who calls COMMIT?? Is $store? And it isn’t. So to solve this problem, we have to replace it with the arrow function
//myVuex.js
class Store{
constructor(options) {
this.vm = new Vue({
data: {state:options.state
}
})
let getters = options.getter || {}
this.getters = {}
Object.keys(getters).forEach(getterName= >{
Object.defineProperty(this.getters,getterName,{
get:() = >{
return getters[getterName](this.state)
}
})
})
let mutations = options.mutations || {}
this.mutations = {}
Object.keys(mutations).forEach(mutationName= >{
this.mutations[mutationName] = (arg) = > {
mutations[mutationName](this.state,arg)
}
})
let actions = options.actions
this.actions = {}
Object.keys(actions).forEach(actionName= >{
this.actions[actionName] = (arg) = >{
actions[actionName](this,arg)
}
})
}
dispatch(method,arg){
this.actions[method](arg)
}
// Modify the code
commit=(method,arg) = >{
console.log(method);
console.log(this.mutations);
this.mutations[method](arg)
}
get state() {return this.vm.state
}
}
Copy the code
To test again
Perfect end !!!!
$store.state.xx = “” $store.state.xx = “” Is that ok? There’s no problem with this assignment, and state is still reactive. So why reinvent the wheel with commit?
Vuex can record every state change record, save state snapshot, implement time roaming/rollback and other operations.
One interesting thing that occurred to me is that if we were to implement the simplest version of Vuex, we would just implement state. No other getters, no action, no commit. It feels like I’m packing light. It can be done.
When implemented, it turns out that state is similar to a global variable, except that state is responsive.
Do you have any questions or suggestions
Thank you also congratulations you see here, I can humble beg a star!!
Github:github.com/Sunny-lucki…