[toc]

Before writing this article, WHEN using Vuex for development, I always had a vague understanding of it. If NOT, I would just copy and paste it and run it. I didn’t know much about the details inside.

Today, I checked a lot of materials by taking advantage of the project, so I want to make Vuex clear from beginning to end.

Before reading this article, I hope you have some basic knowledge of Vue and Vuex. If not, please go to Vue official website

It is inevitable that there are mistakes in the writing, you don’t mind, I hope to point out mistakes ~~

A, the state

Take a look at the standard Store directory structure

Once in VUEX, we need to define variables in state, similar to data in VUE, to store the shared 1 state through state

-store
 --actions   
 --mutations
 --getters
 --mutations-type
 --index.js
 
Copy the code

Index.js is the general entry point

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

import state from './state.js'
import actions from './actions.js'
import mutations from './mutations.js'
import getters from './getters.js'


export default new Vuex.Store({
  state,
  actions,
  mutations,
  getters
  
})

Copy the code

We define variables in state.js

export default {

  userInfo: {userName:"Fried Pikachu.".age:"22".sex:"Female"
  },
  token:"aerfrftregtyrb.rytyuhjyi".friends: ["Tom"."Herry"."Lucy"].likes: ["Eat".'sleep'.'Go to the movies'].money:250
}
Copy the code

To use in components, customize two components in app.vue

<div id="app">
    <about> </about>
    <home> </home>
</div>
Copy the code

About. Vue

<h1>{{$store.state.userInfo.userName}}</h1> // Deep-fried pikachuCopy the code

The contents of home.vue

<h1>{{$store.state.userInfo.age}}</h1> // 22Copy the code

With Vuex, we don’t have to worry about passing values between components. We can get different data directly from $store

But if we need more than one data in VUEX, and this is too verbose, we can define it in computed.

Initialization of props, Methods,data, and computed is done between beforeCreated and created.

For example,

<template>
  <div class="home">
    {{userName}}
  </div>
</template>
<script>
export default {
  name: 'home'.computed: {userName(){
      return this.$store.state.userInfo.userName
    }
  }
}
</script>
Copy the code

That’s a lot easier to introduce.

1.2 mapState auxiliary function

Although it is very convenient to incorporate values in state into a computed object using this.$store.state, the following happens if you take multiple values

computed:{
    userInfo(){
      return this.$store.state.userInfo
    },
    
   token(){
      return this.$store.state.token
    },
    
     friends(){
      return this.$store.state.friends
    },
    
    likes(){
     return this. $store. State. Likes},... }Copy the code

If there are too many, it can be very troublesome, and vuex provides us with a much simpler method called mapState,

This method automatically maps the required state value to the calculated properties of the instance

We could write it like this

import {mapState} from 'vuex'
export default {
  name: 'home'.computed: mapState(['likes'.'friends'.'token'.'userInfo'])}Copy the code
mapState(['likes'.'friends'.'token'.'userInfo']) // Which fields you fill in will automatically map those fields to a calculated property
Copy the code

so

 mapState(['likes'.'friends'.'token'.'userInfo'])

Copy the code

Equivalent to the following code

userInfo(){
  return this.$store.state.userInfo
},

token(){
  return this.$store.state.token
},

 friends(){
  return this.$store.state.friends
},

likes(){
 return this. $store. State. "likes"},Copy the code

Remember: when using helper functions such as mapState, the preceding method name is the same as the name of the fetched property.

What if we need to customize a calculated property? How do I add it?

After all, computed: mapState([‘likes’,’friends’,’token’,’userInfo’])

This is where we need the es6 expansion operator:… . Append the computed properties of the mapState map instead of overwriting the existing ones

computed: {   
  value(){
   return this.val++ }, ... mapState(['likes'.'friends'.'token'.'userInfo'])}Copy the code

Used in templates

<h2>{{userInfo.userName}}</h2> // Deep-fried pikachuCopy the code

About the alias

Sometimes, when we are mapping, we want to give a new name to the computed property instead of the original property name. We can alias the property as an object

computed: {   
  value(){
   return this.val++ }, ... mapState({myLikes:"likes".myFriends:"friends".theUserInfo:"userInfo"})}Copy the code

So we can use it in the template

<h2>{{theUserInfo.userName}}</h2> // Deep-fried pikachuCopy the code

Second, the getters

Getters is the equivalent of a calculated property in vUE that gets the latest value in state

And getters allows arguments, the first of which is state

So, with getters, we get the value we want,

getters.js

export default{
    
    realName:(state) = >{
      return state.userInfo.userName+' '
    },
    myMoney:(state) = >{
      return (state.money/7).toFixed(2)}}Copy the code

We can use this in our example

computed: {  
 valued(){
   return this.value++
 },
 realName:this.$store.getters.realName,
 myMoney:this.$store.getters.myMoney,
}

Copy the code

2.2 mapGetters auxiliary function

The mapGetters function has the role of mapState, and its main usage is the same, mapping the properties of getters to the calculated properties of the instance

computed: {  
 valued(){
   return this.value++ }, ... mapGetters(['realName'.'myMoney'])}Copy the code

You can also take an alias

computed: {  
 valued(){
   return this.value++ }, ... mapGetters({myRealName:"realName".myFormatMoney:"myMoney"})}Copy the code

But one has to ask, they’re both really the same thing, right? Which one? Or is there any real difference?

As mentioned above, getters can pass parameters, which is the difference between getters and mapState

So how do you pass in the parameters?

The first argument to getters is state

Here’s a scenario where we don’t want to take all the friends values in the state, but rather we want to filter them based on the values we pass in

So all we have to do is put a layer of function on the value that’s returned from the getter and then we can do that

export default{
    
    realName:(state) = >{
      return state.userInfo.userName+' '
    },
    myMoney:(state) = >{
      return (state.money/7).toFixed(2)},myFriend:(state) = >{
         // Returns a function to receive the filter value we pass in
         return function (friendName) {
           return state.friends.find(item= > item === friendName)
       }
    }
}

Copy the code

Obviously, getters can not only get the value in state like mapState, but also do what we want to do with the value before we get it

To “derive” new values to meet our different business needs

Not surprisingly, if you don’t need to derive new values,

thisThe.$store.getters. Value is equal tothis. $store. State. The valueCopy the code

Third, mutation

We need some mutations for the definition in the code, which is similar to methods in VUE,

Mutations require a commit to call its methods, and it can also pass in parameters, the first of which is state, and the second is payLoad, which is an additional parameter

We can only change the value of state using mutation

mutations.js

export default {
    addAge:(state,payLoad) = >{
     state.userInfo.age+=payLoad.number
   },
   
   changeName:(state,payLoad) = >{
     state.userInfo.userName+=payLoad.userName
   },
   
   addFriend:(state,payLoad) = >{
      if(state.friends.indexOf(payLoad.newFriend) < 0){
          state.friends.push(payLoad.newFriend)
      }
   },
   
   setUserInfo:(state,payLoad) = >{
       state.userInfo=payLoad.userInfo
   },
   
   setToken:(state,payLoad) = >{
       state.token=payLoad.token
   }
    
}

Copy the code

Used in templates

<div class="home"> < button@click ="handleAddAge"> Add a new friend </ button@click ="handleAddFriend">Copy the code

Js part

methods:{
 handleAddAge(){
 Instead of calling state and getters directly, commit mutation with the commit keyword
   this.$store.commit('addAge', {number:1})},handleAddFriend(){
     let name="Pikachu";
     this.$store.commit('addFriend', {newFriend:name
   })
     
 }
}
Copy the code

The second argument is best written as an object when called, so we can pass more information.

this.$store.commit('mutationName', {key1:val1,
  key2:val2,
  key3:val3
})
     
Copy the code

However, this writing will still encounter the same problem, that is, if you need to operate multiple data, it will become troublesome, at this time we need mapMutations, through it to map the method

3.1 mapMutations auxiliary function

Same thing as mapState, mapGetters

The difference is that mapMutations maps all the methods in mutations to the methods in instance methods

So we can use it this way

methods:{ ... mapMutations(['addAge'])}Copy the code

MapMutations ([‘addAge’]) is equivalent to the following code


methods: {addAge(payLoad){
 
  this.$store.commit('addAge',payLoad)
 }
}

Copy the code

Parameters we can write when this method is called

<button @click="AddAge({number:1})"</button>Copy the code

You can also have aliases

methods:{ ... mapMutations({handleAddAge:'addAge'})}Copy the code
<button @click="handleAddAge({number:1})"</button>Copy the code

At this time, some people will say, why should I go around and change state from mutations? Can I just change state?

Like this:

addAge(){
 this.$store.state.userInfo.age +=5;
}
Copy the code

Actually, the result is ok, so why do I transfer it from mutations?

Here’s why:

  • Mutations does more than just do assignments

  • Vue. Js has a similar buried point operation in mutations. If it is operated from mutations, it can be detected, and it can be more convenient to use debugging tools, which can detect real-time changes, while directly changing the properties in state cannot be monitored in real time

Note: Mutations can only write synchronous methods, not asynchronous, such as AXIos, setTimeout, etc., which cannot be written. The main function of mutations is to modify state.

The reason is similar: if written asynchronous in mutations, it can also be successfully adjusted, but because it is asynchronous, it cannot be traced by debugging tools, so it is not recommended to write so, which is not conducive to debugging, which is the official agreement.

3.2 Replacing Mutation event types with constants

Converts the original method name from a string to a constant

mutations.js


const ADD_AGE ='addAge'
const CHANGE_NAME ='changeName'
const ADD_FRIEND='addFriend'
const SET_USER_INFO='setUserInfo'
const SET_TOKEN='setToken'

// Then replace the old method name with a constant

export default {
    [ADD_AGE](state,payLoad){
     state.userInfo.age+=payLoad.number
   },
   
   [CHANGE_NAME](state,payLoad){
     state.userInfo.userName+=payLoad.userName
   },
   
   [ADD_FRIEND](state,payLoad){
      if(state.friends.indexOf(payLoad.newFriend) < 0){
          state.friends.push(payLoad.newFriend)
      }
   },
   [SET_USER_INFO](state,payLoad){
      state.userInfo=payLoad.userInfo
   },
   
   [SET_TOKEN]:(state,payLoad) = >{
       state.token=payLoad.token
   }
    
}

Copy the code

Why do YOU write that?

  • It is not easy to write wrong, strings are easy to write wrong, and when a string is written wrong, it will not report an error position. Instead, it will use a constant. If it is written wrong, ESLint will indicate an error position
  • When using an Action mutation, use the same constants in the action to avoid hand-slip-write errors

Replace mutations with constants. We can create a new file (mutation_type. Js) that stores these constants

Mutation_type. Js part


const ADD_AGE ='addAge'
const CHANGE_NAME ='changeName'
const ADD_FRIEND='addFriend'
const SET_USER_INFO='setUserInfo'
const SET_TOKEN='setToken'

export  {
   ADD_AGE,
   CHANGE_NAME ,
   ADD_FRIEND,
   SET_USER_INFO,
   SET_TOKEN
}
Copy the code

And then it’s introduced in retro.js

import {
    ADD_AGE,
    CHANGE_NAME,
    ADD_FRIEND,
    SET_USER_INFO,
    SET_TOKEN
} from "./mutation_type.js"

export default {
    [ADD_AGE](state,payLoad){
     state.userInfo.age+=payLoad.number
   },
   
   [CHANGE_NAME](state,payLoad){
     state.userInfo.userName+=payLoad.userName
   },
   
   [ADD_FRIEND](state,payLoad){
      if(state.friends.indexOf(payLoad.newFriend) < 0){
          state.friends.push(payLoad.newFriend)
      }
   },
   [SET_USER_INFO](state,payLoad){
      state.userInfo=payLoad.userInfo
   },
    [SET_TOKEN]:(state,payLoad) = >{
       state.token=payLoad.token
   }
    
}

Copy the code

Fourth, the actions

Action is similar to mutation

Just a few things to keep in mind:

  • The action can commit mutation and then mutation to change the state
  • Action does not operate on state directly, but mutation
  • Actions contain asynchronous operations, similar to AXIOS requests, that can be written in the action
  • Methods in action are asynchronous by default and return promises

Why is that? Because it’s Vuex

actions.js

import {
    ADD_AGE,
    CHANGE_NAME,
    ADD_FRIEND,
    SET_USER_INFO,
    SET_TOKEN
} from "./mutation_type.js"

export default{

   
   // Define an action that asynchronously retrieves user information
    
    async getUserInfo(context){
       // Context can be understood as an object of the entire Store. Similar to this.$store, which contains state, getter, mutations, and actions
       const res = await axios.get('/ interface url')
       
       // mutation_type. Js is used
       context.commit( SET_USER_INFO,{userInfo:res.userData}
       )
    },
    
    // Define an action that asynchronously obtains the user's token
    async getToken(context){
         const res = await axios.get('/ interface url')
         context.commit(
             SET_TOKEN,
             {token:res.token}
       )
    }
    
}

Copy the code

Of course, we can deconstruct the properties of the context by destructing them

Async getUserInfo:({commit,dispatch})=>{const res = await axios.get('/ interface url') commit(SET_USER_INFO, {userInfo:res.userData}) }Copy the code

Consider a real development scenario where the userInfo property value in state is empty, and the corresponding information is retrieved after login.

After login, you need to get the user information displayed on the interface, how to get it?

Call the getUserInfo method in actions when you first enter the page

Vue part


<template>
    <div>{{realName}}</div>
</template>



export default{
    computed(){
    / / the third step
    realName:this.$store.getters.realName
},


created(){ 
    / / the first step
    this.reqUserInfo()
},
methods: {reqUserInfo(){
       // Use the dispatch keyword to dispatch the action instead of invoking state and getters
        this.$store.dispatch('getUserInfo')}}}Copy the code

Let’s go through the above process

  1. Called when the page is initializedthis.reqUserInfo()Methods.this.reqUserInfo()Distribute agetUserInfoThe action of
  2. ingetUserInfoIn this action, we do the following:
 async getUserInfo(context){
      //1. Obtain data asynchronously from the interface
      const res = await axios.get('/ interface url')
      
      //2. Commit a mutation to change the state userInfo
      context.commit( SET_USER_INFO,{userInfo:res.userData})
   },
Copy the code
  1. A user name that gets up-to-date user information in computed
// get the latest userInfo realName:this.$store.getters. RealNameCopy the code

So you should understand the one-way data flow of Vuex

The interface — > dispatch action — > Action submit mutation — >mutation change state — >getters returns the latest state value to the interface

4.1 mapActions auxiliary functions

MapActions, like mapMutations, maps methods from actions to methods

So, when we have more actions, we can use them like this

methods:{ ... mapActions(['getUserInfo'.'getToken'])}Copy the code

The equivalent of

methods:{
    getUserInfo(){
        return this. $store. Dispatch (' getUserInfo ')},getToken(){
        return this. $store. Dispatch (" getToken ")},}Copy the code

You can also have aliases

methods:{ ... mapActions( {reqUserData:'getUserInfo'},
      {reqToken:'getToken')}},Copy the code

By the way, I should add

Additional Actons can be distributed on action, as follows

 async getUserInfo(context){
    
       const res = await axios.get('/ interface url')
       context.commit( SET_USER_INFO,{userInfo:res.userData})
       
       // Send another action here
       context.dispatch('getToken')},Copy the code

5. To sum up

  • Depending on state to get new data, use this.$store.getters. value
  • To get the state data directly, use this.$store.state. value
  • Use this. codestore.mit (‘mutation value ‘) to synchronize the value of the state property.
  • Asynchronously modify state with this.$store.dispatch(‘action value ‘)
  • Auxiliary functions such as mapState, mapMutations, mapGetters, and mapActions allow us to handle multiple methods and attributes