This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
preface
Official explanation: 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. Unofficial explanation: To put it more simply, to provide a solution for unifying the management of data and the ability to re-render the page as the data changes.
Article Demo Download
The demo used in this tutorial has been uploaded to git repository. If you need it, you can download it by yourself. Portal 🚂 click send 🏍
The demo used in this tutorial has been uploaded to git repository. If you need it, you can download it by yourself. Portal 🚂 click send 🏍
The demo used in this tutorial has been uploaded to git repository. If you need it, you can download it by yourself. Portal 🚂 click send 🏍
Why Vuex
Let’s look at two images to understand why Vuex is used
Component system is an important concept of Vue. Under a standard Vue page, there will be nested sub-components, so the data in the page will need to be passed down layer by layer. If the data of the header component needs to be passed to the content component on the left, it will become more complicated.
The emergence of Vuex solves this complicated parameter transfer mode. We can think of Vuex as a global singleton, independent of all components, which can use the data source in Vuex. There would be no such problem.
Create a project and introduce Vuex
1.1 installation Vuex
Enter the following command in the terminal
npm install vuex --save
Copy the code
1.2 Creating files
Create the store folder in the SRC folder and create a new index.js file. Type the following code to create a store
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},mutations: {},actions: {},modules: {}})Copy the code
1.3 injection Vuex
To provide the created store for the Vue instance, write the following code in the mian.js file by “injecting”
import Vue from 'vue'
import App from './App.vue'
import store from './store' // Key code 1
Vue.config.productionTip = false
new Vue({
store, // Key code 2
render: h= > h(App)
}).$mount('#app')
Copy the code
Now that you’ve done the basics, you’re ready to learn and use Vuex.
2. Vuex core concepts
The name of the | explain |
---|---|
State | Single state treeIn fact, it is the data source used to store data, which can be regarded as data in the VUE instance |
Getters | It can be thought of as a calculated property in a VUE instance. It can return processed State data, and the return value is cached based on its dependency value, and is recalculated only if its dependency value changes. |
Mutations | Used to define a function that changes State data and must beSynchronization functionTo change the Vuex storeState (State)The only way is to commit mutation |
Actions | Used to perform asynchronous operations. Since Mutation must be a synchronous function, when you need to change data asynchronously, you need to commit Mutation asynchronously with Action, which then performs the data change |
Modules | Used to split stores intoModuleEach module contains state, mutation, action, getter, and even nested submodules and is segmented the same way from top to bottom |
Let’s take a look at the overall basic code structure
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},getters: {},mutations: {},actions: {},modules: {}})Copy the code
2.1 the State
Let’s look at a few examples of how state is used. Let’s start by defining some default data
export default new Vuex.Store({
state: {
title1:"This is heading 1.".title2:"This is heading 2.".title3:"This is heading 3.",}})Copy the code
The basic use
Method 1: use it directly in double parentheses Method 2: Assign value to data in the lifecycle hook function Method 3: introduce it in the calculation property
<template>
<div id="app">
<! -- Method 1: Use double parentheses directly -->
<h1>{{$store.state.title1}}</h1>
<h2>{{title2}}</h2>
<h3>{{title3}}</h3>
</div>
</template>
<script>
export default {
name: 'App'.data(){
return{
title2: ""}},created(){
// Method 2: Assign data to the lifecycle hook function
this.title2 = this.$store.state.title2
},
computed: {
// Method 3: introduce it in the calculation properties
title3 () {
return this.$store.state.title3
}
}
}
</script>
Copy the code
Extended use (mapState)
This.$store.state.*** when we have more than one data, this.$store.state.*** produces the following code, which looks ugly and redundant.
computed: {
title () {
return this.$store.state.title1 + this.$store.state.title2 + this.$store.state.title3
}
}
Copy the code
To solve that problem, we can use the mapState helper function. Since mapState returns an object, we can use the expansion operator and mix it with local computed properties.
Usage:
data(){
return{
mark:"mapState"}},computed: {
// The original calculated property
title () {
return "Original calculated attribute"
},
// Use the expansion operator. mapState({// This is the only way to get the relevant data
title1(state){
return this.mark + state.title1;
},
// Use the arrow function to simplify the code
title2: state= > state.title2,
Title3 => state.title3
title3: 'title3',}})Copy the code
Use 2:
MapState also supports mapping returns using string arrays as follows:
computed: {
// Using an array return is simpler, but no data manipulation is possible. mapState(['title1'.'title2'.'title3'])}Copy the code
Add a new state property
The state (data) stored in Vuex’s Store is reactive, so when we change the state (data), the state (data) in the component binding changes with it. Set (obj,’newName’,’ new data added ‘) or object extension operators should be used to add or replace old objects when adding new state attributes.
// Set a new parameter
this.$set(this.$store.state,"obj", {name:'name'.name2:Name of '2'})
console.log("Set a new parameter :".this.$store.state.obj)
// Use the expansion operator to set new parameters
this.$store.state.obj = { ... this.$store.state.obj,name2:"Modified Name 2".name3:"Name 3"
}
console.log("Use the expansion operator to set a new parameter :".this.$store.state.obj)
Copy the code
Effect:
2.2 Getters
We can think of Getters as a computed property of the store. It has the same properties as computed properties. The return value of the getter is cached based on the dependency value, and it is recalculated and returned when the dependency value changes
The basic use
There are four parameters in the Getter: state, getters, rootState, and rootGetters. The parameters are described as follows
parameter | instructions | The data type |
---|---|---|
state | Parameter to accessvuex State stored in (data source) |
Object |
getters | Parameter to accessvuex In the othergetter |
any |
rootState | Parameter is used to accessThe root nodeStored state (data source) | Object |
rootGetters | Parameter is used to accessThe root nodeThe rest of thegetter | any |
Getter(state: {}, getters: any, rootState: {}, rootGetters: any)
Copy the code
RootState and rootGetters will not be explained here until we learn Modules
Next, use an example to understand the use of the state and getters parameters
Define the following getters
🥇 concatenates title1 and title2 using the state parameter in getTitle
🥈 concatenates getTitle and title3 using the getters and state arguments in getTitleAll
export default new Vuex.Store({
state: {
title1:"This is heading 1.".title2:"This is heading 2.".title3:"This is heading 3.",},getters: {
// Concatenate title1 and title2 properties with the state argument
getTitle(state){
return state.title1 + state.title2;
},
// Use the getters argument to concatenate getTitle and title3
getTitleAll(state,getters){
returngetters.getTitle + state.title3; }}})Copy the code
Used in vUE components as follows.
<template>
<div>
<! -- Method 1: Direct use -->
<h1>{{ $store.getters.getTitle }}</h1>
<h2>{{ titleAll }}</h2>
</div>
</template>
<script>
export default {
computed: {
titleAll(){
$this.$store.getters.**
return this.$store.getters.getTitleAll; }}}</script>
Copy the code
The problem with getters is that there are no parameters in getters to receive parameters. So how do we implement the parameters in getters
Returns a function that receives the parameters from the getter. The effect is the same in either of the following ways
🥈 First method, for those of you who are not familiar with arrow functions
🥇 The second way, using arrow functions, makes the code look more concise
export default new Vuex.Store({
state: {
title1:"This is heading 1.",},getters: {
// Returns a function that takes a pass argument
splicingTitle(state){
return function(value){
returnstate.title1 + value; }},// Use arrow functions and accept passed object arguments
splicingTitle2: (state) = > (obj) = > state.title1 + obj.value,
}
})
Copy the code
Usage:
<template>
<div>
<h3>{{ splicingTitle }}</h3>
<h3>{{ splicingTitle2 }}</h3>
</div>
</template>
<script>
export default {
computed: {
splicingTitle(){
// Pass string arguments
return this.$store.getters.splicingTitle("This is the parameter that was passed.");
},
splicingTitle2(){
// Pass object parameters
return this.$store.getters.splicingTitle2({value:"Object pass parameter"}); }}}</script>
Copy the code
Extended use (mapGetters)
The mapGetters helper function maps the getters in the store to local computed properties
It should be noted that if it is returned as a string, it is directly used as data. If it is returned as a function, it is called as a function:
<template>
<div>
<! Return string as data -->
<h1>{{ getTitle }}</h1>
<h1>{{ allTitle }}</h1>
<! Call as a function if it returns a function -->
<h1>{{splicingTitle(" parameter A")}}</h1>
<h1>{{ splicingTitle2({value}) }}</h1>
</div>
</template>
<script>
// Start with mapGetters
import { mapGetters } from 'vuex'
export default {
data(){
return{
value:"Parameter B"}},computed: {
// Mix getters into a computed object using the object expansion operator
// Array mapping. mapGetters(['getTitle'.'splicingTitle']),
// Object mapping. mapGetters({// Take another alias map
allTitle:'getTitleAll'.splicingTitle2:'splicingTitle2',}}}</script>
Copy the code
2.3 Mutations
The only way to change the state in a store in Vuex is to commit mutation, which is not a function call, but a commit using store.mit.
Each mutation has a string-typed type and a callback handler. The callback takes state as the first argument and subsequent arguments as custom incoming arguments, but most of the incoming arguments are passed as objects, as discussed later.
The basic use
Two mutations are defined in advance for the rest of the discussion
export default new Vuex.Store({
mutations: {
// updateTitle is the type and the arrow function is the mutation callback
"updateTitle":(state) = >{
state.title1 = "Mutation modified title 1"
},
// Short form, passing in extra arguments
updateTitle2(state,obj){
state.title2 = "Mutation modified title 2"}}})Copy the code
The following example shows how to use store.mit. There are three types of mutations listed here. There is no problem with using any of them.
<template>
<div>
<h2>{{$store.state.title1}}</h2>
<button @click="updateTitle">Modify state (without passing additional parameters)</button>
<h2>{{$store.state.title2}}</h2>
<button @click="updateTitle2">Modify state (pass extra parameters)</button>
<button @click="updateTitle3">Modify state (passing extra parameters as objects)</button>
</div>
</template>
<script>
export default {
methods: {updateTitle(){
// Commit directly without parameters
this.$store.commit("updateTitle")},updateTitle2(){
// Submit and take an object as an extra parameter
this.$store.commit("updateTitle2", {value:"Additional parameters"})},updateTitle3(){
// Submit as an object with parameters
this.$store.commit({
type:"updateTitle2".value:"Submit additional parameters as objects"})}}}</script>
Copy the code
Open access (mapMutations)
We have talked twice about similar auxiliary functions above, and we will not repeat them here. The auxiliary function of mapMutations will be introduced first, and then the function will call the function, and the non-function will be directly used
<template>
<div>
<h2>{{$store.state.title1}}</h2>
<button @click="updateTitle">Modify state (without passing additional parameters)</button>
<h2>{{$store.state.title2}}</h2>
<button @click="updateTitle2({value:'mapMutations'})">Modify state (pass extra parameters)</button>
</div>
</template>
<script>
// introduce the mapMutations auxiliary function
import {mapMutations} from 'vuex'
export default {
methods: {... mapMutations(['updateTitle'.'updateTitle2']),}}</script>
Copy the code
Mutation must synchronize the function
Initially, we said that mutation must be a synchronization function. Why must it be a synchronization function? I will take an example to explain.
We first define two mutations as synchronous and asynchronous
mutations: {
// synchronization function
addcount(state){
state.count++;
},
// Asynchronous functions
syncAddcount(state){
setTimeout(() = > {
state.count++;
}, 1000); }},Copy the code
Let’s call and observe the Vue Devtools panel in the page. If you have not installed the Vue Devtools panel, please baidu to install it
<template>
<div>
<h2>{{$store.state.count}}</h2>
<button @click="addcount">Commit synchronization method</button>
<button @click="syncAddcount">Commit asynchronous methods</button>
</div>
</template>
<script>
export default {
methods: {addcount(){
this.$store.commit("addcount")},syncAddcount(){
this.$store.commit("syncAddcount")}}}</script>
Copy the code
Vue Devtools panel:
When we execute the synchronization function, we can see that the execution record normally occurs and the count value in state normally changes
When we execute the asynchronous function again, we can see that the execution record is there, but the count value in state has not changed, but the count value on the page is normally displayed as the number 2
From the above case, we found that it was very difficult to debug the program when mixing asynchronous calls in mutation. Therefore, Actions can solve such asynchronous calls well for the next section
2.4 the Actions
The primary role of actions is to commit mutation rather than directly change state (data) as mutation does, and actions support the inclusion of arbitrary asynchronous operations.
The Action function takes a context object as its first argument. The context object has the same methods and properties as the store instance. You can think of it as a store.
The basic use
The mutation must be a synchronous function, and the action is not subject to this constraint
actions: {
// when addcountAction is executed, a mutation is committed
addcountAction(context){
context.commit("addcount");
},
/ / asynchronous Actions
syncAddcountAction(context){
setTimeout(() = > {
context.commit("addcount");
}, 1000); }},Copy the code
Action is triggered by the store.dispatch method:
<template>
<div>
<h2>{{$store.state.count}}</h2>
<button @click="addcountAction">Executing synchronous Action</button>
<button @click="syncAddcountAction">Executing asynchronous actions</button>
</div>
</template>
<script>
export default {
methods: {addcountAction(){
this.$store.dispatch("addcountAction")},syncAddcountAction(){
this.$store.dispatch("syncAddcountAction")}}}</script>
Copy the code
Next, let’s observe the data changes through the Vue Devtools panel
The sync Action function was executed and a mutation of type addCount was submitted, where the data changes were fine
Next, we executed the asynchronous Action function, and the function also submitted a mutation of type addcount, which also changed the data normally. So far, the mutation could not execute the asynchronous function.
Mutation can carry parameters, so what about Action? The use of the Action carry parameter is the same as that of mutation.
Define the action
// Carry parameters
updateTitle2Action(context,obj){
context.commit({
type:"updateTitle2".value:obj.value
});
}
Copy the code
Call to action
this.$store.dispatch({
type:'updateTitle2Action'.value:'Action parameter pass'
})
Copy the code
Extended use (mapActions)
This is another helper function. Every core concept in Vuex has such a helper function, and the usage is basically the same. Just paste the code directly
<template>
<div>
<h2>{{$store.state.count}}</h2>
<button @click="addcountAction">Executing synchronous Action</button>
<button @click="syncAddcountAction">Executing asynchronous actions</button>
<h2>{{$store.state.title2}}</h2>
<button @click="btnClick">Action with parameters</button>
</div>
</template>
<script>
/ / introduce mapActions
import {mapActions} from 'vuex'
export default {
methods: {... mapActions(['addcountAction'.'syncAddcountAction'.'updateTitle2Action']),
// If you need to carry parameters, you can write it like this
btnClick(){
this.updateTitle2Action({
value:'Action parameter pass'})}}}</script>
Copy the code
Combine Action (asynchronous extension)
We know that actions can be executed asynchronously, so how do we know when an asynchronous task is finished? How do we know if we need to combine multiple actions?
The first thing we need to know is that store.dispatch itself will return a Promise. If the Action executed does not return a Promise, it will return a Promise after successful execution. If the Action executed returns a Promise, the returned Promise is processed. So let’s take a look at the following example
Let’s define three actions to be
🥇 combinationA asynchronously executes an action without returning a Promise
🥈 combinationB asynchronously executes the action and returns the Promise
🥉 combinationC waits for the completion of combinationB execution
// Asynchronous actions do not return promises
combinationA(context){
setTimeout(() = > {
context.commit("addcount");
console.log("CombinationA execution completed");
}, 2000);
},
// Asynchronous action returns Promise
combinationB(context){
return new Promise((resolve) = >{
setTimeout(() = > {
context.commit("addcount");
console.log("CombinationB execution completed.");
resolve();
}, 2000); })},// Combine action, combinationB and then combinationC
combinationC(context){
return new Promise( resolve= >{
context.dispatch("combinationB").then(() = >{
context.commit("addcount");
resolve();
console.log("CombinationC execution completed"); })})}Copy the code
Each of the three actions has a corresponding console output, so let’s look at the final execution steps in conjunction with store.dispatch
<template>
<div>
<h2>{{$store.state.count}}</h2>
<button @click="btnClick1">combinationA</button>
<button @click="btnClick2">combinationB</button>
<h2>{{$store.state.title2}}</h2>
<button @click="btnClick3">combinationC</button>
</div>
</template>
<script>
import {mapActions} from 'vuex'
export default {
methods: {... mapActions(['combinationA'.'combinationB'.'combinationC']),
btnClick1(){
this.combinationA({
value:'Action parameter pass'
}).then(res= >{
console.log("Dispatch executed successfully",res)
}).catch(err= > {
console.log("Dispatch execution failed",err)
})
},
btnClick2(){
this.combinationB({
value:'Action parameter pass'
}).then(res= >{
console.log("Dispatch executed successfully",res)
}).catch(err= > {
console.log("Dispatch execution failed",err)
})
},
btnClick3(){
this.combinationC({
value:'Action parameter pass'
}).then(res= >{
console.log("Dispatch executed successfully",res)
}).catch(err= > {
console.log("Dispatch execution failed",err)
})
}
}
}
</script>
Copy the code
Review the definitions of the three actions and see if the console output is correct
🥇 combinationA asynchronously executes an action without returning a Promise
🥈 combinationB asynchronously executes the action and returns the Promise
🥉 combinationC waits for the completion of combinationB execution
2.5 the Module
Coming to the last core concept Module, after learning the above knowledge, it is not difficult to find that when the application state (data) is very large and all concentrated together, it will become very difficult to manage. In order to solve this problem, Vuex allows us to divide store into modules. Each module has its own state, mutation, action, and getter. Let’s familiarize ourselves with the core concept of Modules through examples.
The basic use
Start by defining two modules in the Modules folder under the Store folder in your project
The code is as follows: these are defined separately in the module
home/index.js
const homeModule={
state:() = >({
username:"Wang".userId:"wdnmd_123456"
}),
getters: {usernameHandle(state){
return "Account Name:+state.username; }}}export default{
// Use the expansion operator to export the core concepts of homeModule. homeModule }Copy the code
order/index.js
let orderModule={
state:() = >({
orderCount:"99"
}),
getters: {orderCountHandle(state){
return "Total orders:"+state.orderCount; }}}export default{
// Export the core concepts of orderModule using the expansion operator. orderModule }Copy the code
Introduce modules in store
// Import the home module
import homeModule from './modules/home/index'
// Import the order module
import orderModule from './modules/orders/index'
export default new Vuex.Store({
modules: {
homeModule,
orderModule
}
})
Copy the code
Now that we have the basic modularity, how can we use it
<template>
<div>
<! $store.state.homemodule.*** -->
<h2>{{$store.state.homeModule.username}}</h2>
<! $store. Getters.*** Without the homeModule module name -->
<h2>{{$store.getters.usernameHandle}}</h2>
</div>
</template>
Copy the code
The namespace
By default, module states are registered in the local namespace, while actions, mutations, and getters are registered in the global namespace. This is why the homeModule module name is not required for getters. State requires the module name
// Get state, action, mutation, and getters in the homeModule module
{{$store.state.homeModule.**}}
{{$store.getters.**}}
{{$store.mutations.**}}
{{$store.actions.**}}
Copy the code
In this case, the same naming between modules will cause a collision, so we can add namespaaced:true to make them namespaced modules (i.e., actions, mutation, and getters are all named according to their module names when registered). Let’s take a look at using modules with namespaces.
First add namespaced:true to orderModule
Let’s print getters on the console
Usage:
this.$store.getters['orderModule/orderCountHandle']
this.$store.actions['orderModule/orderAction']
this.$store.mutations['orderModule/orderMutation']
Copy the code
Note: Nested child modules in modules that do not use namespaces inherit their parent namespaces by default
RootState and rootGetters parameters
As mentioned above, there are four parameters in Getter: state, getters, rootState and rootGetters. We have already mentioned the first two parameters. In Module, we will talk about rootState and rootGetters
First modify the orderCountHandle in Orders to see the normal print information
orderCountHandle(state,getters,rootstate,rootgetters){
console.log("Rootgetters in Orders:",rootgetters);
console.log("Rootstate in orders:",rootstate);
return rootgetters.getTitle;
}
Copy the code
According to the printed data above, the two parameters rootState and rootGetters respectively represent the state and getters of the root node, so that we can obtain the corresponding functions or attributes in other modules.