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 accessvuexState stored in (data source) Object
getters Parameter to accessvuexIn 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.