What is a Vuex

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.

Install vuex

// Install NPM I vuex yarn add vuex using NPM or YARNCopy the code

Using Vuex

Let’s start with a simple example of Vuex

import Vue from 'vue'
import Vuex from 'vuex'

const store = new Vuex.Store({
  // Define state, all state stored objects
  state: {
    count: 0
  },
  
  // The only way to change state is to commit mutation
  mutations: {
    plusOne(state) {
      console.log(state);
      if(state? .count ===0) {
        state.count++;
      } else {
        state *= 2; }}}});// After two seconds, commit the plusOne action and print the state object. Expect state.count to be +1
setTimeout(() = >{
    store.commit("plusOne");
    console.log(store.state);
},2000)
// State is reactive and updates the corresponding view if used in a component
Vuex is similar to a global Window object, so the state is stored in state. Other instance methods are used to obtain, modify, and disassemble the decoupled module.
Copy the code

state

State is defined as an object, and state in VUEX follows the same rules as data in VUE

Access to the state

Since state is reactive, the easiest way to do this is to return data by evaluating properties, provided that when the root component new Vue passes a Store object in the construct option, the child component can perform method state through this.$store

/ / in the main. In js
const store = new Vuex.Store({
    state: {age:10}})const app = new Vue({
    store,
    render: h= > h(App),
})
app.$mount('#app')


// In the child component
export default {
    computed: {getInfo(){
            // Return the age stored in state
            return this.$store.state.age
        }
    }
}
Copy the code

MapState helper function

When a component needs to fetch multiple states, it can be repetitive and redundant to declare all those states as computed properties. To solve this problem, we can use the mapState helper function to help us generate calculated properties that will save you from pressing the key:

  • The mapState function returns an object. The function receives an object or an array of strings. The value of an attribute in an object can be written either as a function or as a string.
  • The string array is passed in the name of the property in state that needs to be mapped
/ / child component
import { mapState } from "vuex";
// Instead of deconstructing, use Vuex. MapState

// mapState uses objects
export default {
  data(){
      return {
          testStr:'This is the test.'}},computed: mapState({
    stateInfo: (state) = > state.age, // Use the arrow function, this is undefined
    stateAlias: "age".// Use a string, equivalent to the arrow function
    stateUseThis(state){ // Use normal functions
        // The component instance can be accessed through this
        return this.testStr + state.age // Returns the string "This is test 10"}})}// mapState uses string arrays
export default {
  // If the calculated property name is the same as the object property name, you can use this.age to access state.age
  computed: mapState(["age"])}Copy the code

Local computed properties mixed with mapState using expansion operators… Expand the return value of mapState (the code for mapState is an example of passing an Object, just as it is for passing a string array), or merge objects using object. assign, as long as you finally return an Object for the computed property

/ / child component
import { mapState } from "vuex";
// Instead of deconstructing, use Vuex. MapState

// Use the expansion operator
export default {
  computed: {
    test() {
      return "This is the test.";
    },
    ...mapState({
      stateInfo: (state) = > state.age, // Use the arrow function, this is undefined
      stateAlias: "age".// Use a string
      stateUseThis(state) {
        // Use normal functions
        // The component instance can be accessed through this
        return this.testStr + state.age; // Returns the string "This is test 10"}}}}),/ / use the Object. The assign
export default {
  computed: Object.assign(
    {
      test() {
        return "test";
      },
    },
    mapState({
      stateInfo: (state) = > state.age,
      stateAlias: "age".stateUseThis(state) {
        return this.testStr + state.age; }}}))Copy the code

getters

The definition of getters is an object, which is similar to computed in Vue. The returned value is cached according to dependencies and recalculated only when the dependency value changes, which can be used as computed in Store. The getter is exposed as the Getters property of the Store instance object, through which the corresponding value can be accessed.

const store = new Vuex.Store({
  state: {
    name: 'mike'.age: 30.children: [{name: 'ben'.age: 1.gender: 'male' },
      { name: 'susan'.age: 2.gender: 'female'}},],// The getter accepts the first argument of state
  getters: {
    getBoys: state= > state.children.filter(child= > child.gender === 'male')
    // Return all male objects in the children array}})// access from the component
export default {
    created(){
        console.log(this.$store.getters.getBoys)
        // Prints the return value of getBoys}}Copy the code

In addition to the state, the getter accepts the entire getters as a second argument

const store = new Vuex.Store({
  state: {
    name: 'mike'.age: 30.privateChildCount: 10.// Number of illegitimate children
    children: [{name: 'ben'.age: 1.gender: 'male' },
      { name: 'susan'.age: 2.gender: 'female'}},],getters: {
    getBoys: (state) = > {
      return state.children.filter(child= > child.gender === 'male')},getTotalChildCount: (state, getters) = > {
      Getters. GetBoys gets the number of boys, plus the number of known illegitimate children (10) to get the total number of biological children
      return state.privateChildCount + getters.getBoys.length
    }
  }
})
Copy the code

The getter returns a function through method access to realize the transfer of parameters. It is used for array query operations in state. The difference is that when the getter accesses a function through method, the result is not cached and the call is executed each time.

const store = new Vuex.Store({
  state: {
    name: 'mike'.age: 10.privateChildCount: 10.children: [{name: 'ben'.age: 1.gender: 'male' },
      { name: 'susan'.age: 2.gender: 'female'}},],getters: {
    // findChildByAge returns a function that receives age to filter data
    findChildByAge: state= > age= > state.children.filter(child= > child.age === age)
  }
})

// called in the component
export default {
    created(){
        // Returns and prints an array of all objects in the children array of state whose age is 2
        console.log(this.$store.getters.findChildByAge(2)); }}Copy the code

MapGetters helper function

Similar to mapState, map getters from store to computed, and receive arguments that can be string arrays or objects, mixed with other locally computed attributes using reference mapState

// Receives an object and renames the getter
export default {
  created(){
    console.log(this.myGetter(2));
  },
  computed: mapGetters({
    // Rename findChildByAge to myGetter
    myGetter: "findChildByAge",}})// Receive a string
export default {
  created(){
    console.log(this.findChildByAge(2));
  },
  / / at this point you can use this. FindChildByAge visit store. Getters. FindChildByAge
  computed: mapGetters(['findChildByAge']),}Copy the code

mutations

Mutations definition is an object, and the only way to change the store state is to submit mutation. Mutation is similar to an event in that both have the name of the event (string) and the callback function, which is where the changed state is to be made, and which accepts state as the first argument.

const store = newe Vuex({
    state: {age:0
    },
    mutations: {addAge(state){
            state.age++
        }
    }
})

Mutations register as an event, rather than calling the callback directly. To perform mutation, use store.mit (mutations).
store.commit("addAge")
// The function named addAge is executed with state +1
Copy the code

You can submit an additional Payload, which is the parameter, when you commit a COMMIT, and accept the Payload when you define mutation. Payload is typically passed into an object (which can be any other data type) that can contain multiple fields and is easy for code to read

const store = newe Vuex({
    state: {age:0
    },
    mutations: {// Payload is a number. Change state.age to state.age+payload
        // addAge(state,payload){ 
        // state.age += payload
        // }Content is the objectaddAge(state,payload){ 
            state.age += payload.age
        }
    }
})

// Pass in the object
store.commit("addAge", {age:10})

/ / the incoming number
//store.commit("addAge",10)
Copy the code

Object – style submission, including the Type attribute

When an object style is used, the entire object is passed to the mutation function as payload. As shown in the code example above, no code changes are required to change the object style

// mutation
addAge(state,payload){ 
    // Payload carries the type attribute
    state.age += payload.age
}

// commit Object style commit
store.commit({type:'addAge'.age:10})
Copy the code

Mutation needs to follow the corresponding rules of Vue, such as:

  1. The properties are pre-initialized in the state object, just as data is used in VUE
  2. When adding a new attribute to a property object, use vue.set for data hijacking listening, or… Assign a new Object with object. assign

It is important to note that mutation must be a synchronization function, otherwise the DEBUG tool cannot capture the snapshot

mapMutations

Map mutation to methods of a Vue instance in the same way as mapGetters, taking an object or an array of strings, and returning an object

const store = new Vuex.Store({
    mutations: {test1(){},
        test2(payload){console.log(payload)}
    }
})

// In the child component
import { mapMutations } from 'vuex';

export default {
    methods: {// Use an array of strings. mapMutations(["test1"."test2"]),
        
        // use the object and rename it with mutation. mapMutations({myAction:'test1'})},// you can submit the mutation name with the name of this. The mutation name is not required. Payload of the method name (payload)
    created(){
      this.test(); Use this. Codesotre.com MIT ('test')
      this.test2('test') // submit the mutation named test2 and pass in payload
      
      this.myAction() // equivalent to this.test}}Copy the code

actions

The definition of Actions is an object. Actions is similar to Mutations. Actions submit mutations, not directly changing state, and can include asynchronous operations. You can issue a COMMIT from an object to get state and getters.

const store = new Vuex.Store({
  state: {
    age: 10
  },
  mutations: {
    addAge: (state, payload) = > {
      state.age += payload.age
    }
  },
  actions: {
    addAge(context) {
      Context has the same property methods as store
      // In action, there are no synchronization restrictions and asynchronous operations can be performed
      setTimeout(() = > {
        context.commit('addAge', { age: 20})},2000)}// The usual destruct method is to deconstruct the commit method from the parameter
    // addAge({ commit }) {
    // commit('addAge', { age: 20 })
    // }}})// In the child component
export default {
    created(){
        // Actions need to be dispatched via dispatch
        this.$store.dispatch("addAge")}}Copy the code

The action supports payload and object distribution. The action mode is the same as that of mutation

Simulate a shopping cart case to experience action usage

// Emulating an interface is invoicing, details omitted, mainly to show asynchrony
const api = {
  shop: {
    buyProducts() {
      return new Promise((resolve, reject) = > {
        setTimeout(() = > {
          try {
            resolve({
              result: null.success: true.error: null})}catch (error) {
            reject(error)
          }
        }, 1000)})}}}// Initiate asynchronous behavior in actions and commit mutation to record state changes.
const store = new Vuex.Store({
  state: {
    // Current shopping cart data
    cartList: [{productName: The word "apple".count: 20.price: 2 },
      { productName: "Watermelon".count: 12.price: 15 },
      { productName: "Durian".count: 3.price: 80}},],mutations: {
    // Empty the shopping cart
    cleanUp(state) {
      state.cartList = []
    },
    // Restore the shopping cart
    checkoutFaild(state, cartList) {
      alert('Settlement failed')
      state.cartList = cartList
      console.log("Shopping cart data restored");
      console.log(state.cartList); }},actions: {
    / / check out
    checkout({ state, commit }) {
      // Back up the shopping cart
      const tempCartList = [...state.cartList]
      
      // Submit a mutation to empty the shopping cart
      commit('cleanUp')
      
      // Suppose an asynchronous request is made and data is returned 1s later
      api.shop.buyProducts().then(res= > {
        // Ignore the payment process and pop up the completion message if it succeeds
        if (res.success) alert('Settlement successful, shopping completed')},() = > {
        // If that fails, restore the shopping cart
        commit('checkoutFaild', tempCartList)
      })
    }
  }
})

// The action is distributed in the component
export default {
    created(){
        this.$store.dispatch('checkout')
        // The demo code is dead, so the expectation after distributing the action is that the settlement success message will pop up}}// Verify the restore shopping cart, simply throw an exception in the try block of the test code in the emulation API, and the mutation of the restore shopping cart will be committed
// Other code ignored
setTimeout(() = > {
    try {
        // Throw an exception here
        throw new Error('I made a mistake on purpose.')}catch (error) {
        reject(error)
   }
}, 1000)

// After the mutation, a failure message will be displayed and the shopping cart data will be restored
Copy the code

mapActions

Map Actions to the Methods of a Vue instance in the same way as mapGetters, taking an object or an array of strings, and returning an object

const store = new Vuex.Store({
    actions: {test1(){},
        test2(payload){console.log(payload)}
    }
})

// In the child component
import { mapActions } from 'vuex';

export default {
    methods: {// Use an array of strings. mapActions(["test1"."test2"]),
        
        // Use the object and rename the action. mapActions({myAction:'test1'})},// Now the subcomponent can distribute the action as this. The action name is not required to display. If there is a payload, place it in this. Payload of the method name (payload)
    created(){
      this.test(); // Dispatch an action named test1, equivalent to this.$sotre.dispatch('test')
      this.test2('test') // Distribute an action named test2 and pass in payload
      
      this.myAction() // equivalent to this.test}}Copy the code

Combination of the Action

Actions are usually asynchronous, so how do you know when an Action ends? More importantly, how can we combine multiple actions to handle more complex asynchronous processes? First, you need to understand that Store. dispatch can handle promises returned by handlers of triggered actions, and that store.dispatch still returns promises.

// In the action handler, return a Promise, rewritten using the shopping cart example above
const store = new Vuex.Store({
  / / state
    
  actions: {
    / / check the action
    checkout({ state, commit }) {
      return new Promise((resolve, reject) = > {
        const tempCartList = [...state.cartList]
        commit('cleanUp')
        api.shop.buyProducts().then(res= > {
          // Ignore the payment process and pop up the completion message if it succeeds
          if (res.success) alert('Settlement successful, shopping completed')
          resolve(true)},() = > {
          // If that fails, restore the shopping cart
          commit('checkoutFaild', tempCartList)
          reject(false)})})},Actions can also be distributed in other actions
    testAction({ dispatch }){
        dispatch('checkout').then(
        (flag) = > console.log(flag),
        (flag) = > console.log(flag) ); }}})// Used in components
export default {
    created(){
        this.$store.dispatch('checkout').then(=>{
            // The action handler is complete, and either resolve or reject returns a Boolean
            (flag) = > console.log(flag),
            (flag) = > console.log(flag)
        })
    }
}
Copy the code

modules

Because of the use of a single state tree, all the states of an application are grouped into one large object. When the application becomes very complex, the Store object can become quite bloated. To solve these problems, Vuex allows us to split the Store into modules. Each module has its own state, mutation, action, getter, and even nested submodules — split the same way from top to bottom

// Change the object passed in to new vuex. Store to the modules object property of the object passed in
const moduleA = {
    state: {id : 0}}const moduleB = {
    state: {id : 1}}const store = new Vuex.Store({
    modules:{
        moduleA,
        moduleB
    }
})

ModuleA and moduleB are properties of the State object of the store instance
// Get the ID of moduleA's state
console.log(store.state.moduleA.id)
Copy the code

Local state of a module

For getters and mutations inside the module, the first argument accepted is a local state object, i.e. state is the module’s own state. For actions inside the module, the local state is exposed via context.state. The state of the root node is exposed through context.rootState for the getter inside the module, the root node is exposed through the third parameter

The module’s namespace

By default, actions, mutations, and getters inside a module are registered in the global namespace — enabling multiple modules to respond to the same mutation or action. However, you can make a module namespaced by adding the attribute namespaced: True to it. When a module is registered, all its getters, actions, and mutations are automatically named according to the path the module was registered with.

const moduleA = {
    namespaced:true.// Add attributes
    state: {// state is not affected
        id : 0
    },
    getters: {getId(state){ / / need to use the store. When external call getters. ModuleA/getId
            return state.id.toString()
        }
    },
    mutations: {// need to use store.mit ('moduleA/plusId') for external calls
        plusId(state){
            state.id++;
            console.log(state.id)
        }
    },
    actions: {// Need to use store.dispatch('moduleA/asyncPlusId') when making external calls
        asyncPlusId({ commit }){
            // With namespace-enabled, the getters/dispatch/commit received in the action are localized, so the call does not need a naming prefix
            setTimeout(() = >{
                commit('plusId') // call mutation with a name
            },2000)}}}const store = new Vuex.Store({
    modules:{
        moduleA
    }
})

// Used in components
export default {
    created(){
        // Get the getter for the namespace
        console.log(this.$sotre.getters['moduleA/getId'])
    
        // commit a mutation, and you need to pass in the [module name /mutation name]
        this.$store.commit('moduleA/plusId')
        
        // Dispatch an action, pass in [module name /action name]
        this.$store.dispatch('moduleA/asyncPlusId')}}Copy the code
Getters [‘ parent module name/submodule name /getter name ‘] is required. If the submodule is not a named module, it does not need to be added

Access global content in the namespace

If you want to use the global state and getter, rootState and rootGetters are passed to the getter as the third and fourth arguments, and the action is passed to the context object’s properties. To distribute action or commit mutation within the global namespace, pass {root: true} as the third argument to Dispatch or COMMIT.

const moduleA = {
    namespaced:true.// Add attributes
    state: {// state is not affected
        id : 0
    },
    getters: {getId(state,getters,rootState,rootGetters){ 
            // getters are local getters that contain only getters for the module itself and its submodules
            // rootGetters are global getters
            // In the local getter, to access the getter of the subnamed module, use [submodule name/submodule getter name]
            return state.id.toString()
        }
    },
    mutations: {// need to use store.mit ('moduleA/plusId') for external calls
        plusId(state){
            state.id++;
            console.log(state.id)
        }
    },
    actions: {// Need to use store.dispatch('moduleA/asyncPlusId') when making external calls
        asyncPlusId({ commit }){
            // With namespace-enabled, the getters/dispatch/commit received in the action are localized, so the call does not need a naming prefix
            setTimeout(() = >{
                commit('plusId') // call mutation with a name
            },2000)}}}Copy the code

Register global actions in namespaced modules

const store = new Vuex.Store({
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction') // Distribute global actions registered under the named module}},modules: {
    modulesA: {
      namespaced: true.actions: {
        globalAction: {
          // Change to object form, pass root:true, write the action in handler function, handler function mapState number of the first argument to receive the current named module
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
        // This action becomes a global action without a naming prefix}}}})Copy the code

A binding function with a namespace

When using mapState, mapGetters, mapMutations, and mapActions mappings, using the original notation is a bit cumbersome

computed: { ... mapState({aId: state= > state.modulesA.id,
        bId: state= > state.modulesA.id
      })
    },
    methods: {
      ...mapActions([
        "moduleB/asyncPlusId" // -> this["moduleB/asyncPlusId"]()])}Copy the code

In this case, you can pass the module’s space name string as the first argument to the above function, so that all bindings automatically use the module as a context. So the above example can be simplified to

computed: { ... mapState("moduleB", {
      bId: (state) = > state.id,
    }),
  },
  methods: {
    ...mapActions("moduleB"["asyncPlusId"]) // -> this.asyncPlusId()
  },
Copy the code

In addition, you can create helper functions based on a namespace by using createNamespacedHelpers. It returns an object with a new component binding helper function bound to the given namespace value

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions , mapMutations , mapGetters } = createNamespacedHelpers('moduleB')
// When the map series of functions are used later, relevant functions will be looked up in moduleB
Copy the code