What is 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.

State

Vuex uses a single state tree — yes, it contains all the application-level state in a single object. At this point it exists as a “unique data source (SSOT)”. This also means that each app will contain only one Store instance. A single state tree allows us to directly locate any particular state fragment and easily take a snapshot of the entire current application state during debugging.

Vuex’s State storage is responsive, and we can trigger DOM updates by computing the property computed, State -> computed ->

import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {msgList: []}})new Vue({
    store,
    / /...
    computed:{
        msgList:function(){
            return this.$store.state.msgList
        }
    }    
})
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

const store = new Vuex.Store({
  state: {count:0}})Copy the code
<template>
    <div>
        <p>{{localCount}}</p>
    </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
    data(){
        return{
            countStr:'the count:}},computed:mapState({
        count: 'count',
        localCount(state){
            return this.countStr + state.count
        }
    })
}
</script>

Copy the code

Getter

Vuex allows us to define getters (you can think of them as computed properties of the store) in the store. Just like evaluating properties, the return value of a getter is cached based on its dependency and is recalculated only if its dependency value changes.

A Getter can take state as the first argument and getters as the second argument, and it can rely on state and getters to evaluate, just as a component evaluates properties. Access through store.getters.

You can make the getter return a function that takes arguments to the getter.

MapGetters helper function

The mapGetters helper function simply maps the getters in the store to local computed properties

const store = new Vuex.Store({
  state: {id:0.count:0
  },
  getters: {countStr:state= >'getCountStr' + state.count,
    getValue:(state,getters) = > value => getters.countStr + state.id + value
  },
})
Copy the code
<template>
    <div>
        <input type="text" name="" v-model="value"  id="">
        <p>{{countStr}}</p>
        <p>{{localGetCount}}</p>
    </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
    data(){
        return{
            value:' '.localCountStr:'the count:}},computed: {... mapGetters(['countStr'
        ]),
        localGetCount(){
            return this.$store.getters.getValue(this.value)
        },
    }
}
</script>

Copy the code

Mutation

The only way to change the state in Vuex’s store is to commit mutation, which is similar to events. Each mutation has an event type of a string, and a callback function, in which the first argument received is state.

The mutation function must be a synchronization function

const store = new Vuex.Store({
  state: {msgList: [].id:0.count:0
  },
  mutations:{
    insertMsg(state,payload){
      var item = {
        id:state.id++,
        msg:payload.msg
      }
      state.msgList.push(item)
      state.count++
    }
  }
})
Copy the code

Instead of calling a mutation handle directly, we can fire events via store.mit (‘insertMsg’)

Object style submission

Another way to commit mutation is to use an object containing the Type attribute directly

this.$store.commit({
    type:'insertMsg',
    msg:this.value
})
Copy the code

The Mutation complies with the Vue response rules

  • It is best to initialize all required properties in your store in advance.
  • When you need to add new properties to an object, you should
    • useVue.set(obj, 'newProp', 123),or
    • The new object replaces the old object. For example, using the stage-3 object expansion operator we could write:
    state.obj = { ... state.obj, newProp: 123 }Copy the code

Action

Action is similar to mutation, except that:

  • The Action commits mutation rather than a direct state change.
  • Actions can contain any asynchronous operation.

Distribution of the Action

store.dispatch('increment'// Distribute store.dispatch('incrementAsync', {amount: 10}) // Distribute store.dispatch({amount: 10})type: 'incrementAsync',
  amount: 10
})
Copy the code

Component distributes the Action

You can use this.$store.dispatch(‘ XXX ‘) to distribute actions in the component, or use mapActions helper functions to map the component’s methods to store.dispatch calls (requiring store injection at the root node first).

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

const store = new Vuex.Store({
  state: {msgList: [].id:0.count:0
  },
  mutations:{
    insertMsg(state,payload){
      var item = {
        id:state.id++,
        msg:payload.msg
      }
      state.msgList.push(item)
      state.count++
    },
  },
  actions:{
    insertMsg({commit},payload){
      return new Promise((resolve,reject) = >{
        setTimeout((a)= > {
          if(Math.random() > 0.5){
            commit('insertMsg',payload);
            resolve();
          }else{
            reject('Insert failed')}},1000); }}}})Copy the code
<! -- Component call -->
<template>
    <div>
        <input type="text" name="" v-model="value"  id="">
        <button @click="handleInsert" >submit</button>
        <p v-for="(item,index) in msgList" :key='index'>
            {{item.id}}:{{item.msg}}
            <button @click='handleRemove(item.id)'>del</button>
        </p>
    </div>
</template>

<script>
import { mapState } from 'vuex'
import { mapGetters } from 'vuex'
import { mapMutations } from 'vuex';
export default {
    data(){
        return{
            value:' '.localCountStr:'the count:}},computed: {... mapState({msgList: state= > state.msgList,
            count: 'count',
            localCount(state){
                return this.localCountStr + state.count
            }
        }),
    },
    methods:{
        handleInsert(payload){
            if(this.value ! = =' ') {this.$store.dispatch({
                    type:'insertMsg'.msg:this.value
                }).then((a)= >{
                    // Successful callback
                    console.log('suss')
                    this.value = ' ';
                },(error)=>{
                    // Failed callback
                    alert(error)
                })
            }
        },
    }
}
</script>
Copy the code

Module

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

const moduleA = {
  state: {... },mutations: {... },actions: {... },getters: {... }}const moduleB = {
  state: {... },mutations: {... },actions: {... }}const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA status
store.state.b // -> moduleB status
Copy the code

The 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.

If you want your modules to be more wrapped and reusable, you can make them namespaced by adding namespaced: True. When a module is registered, all its getters, actions, and mutations are automatically named according to the path the module was registered with.

const child = {
  namespaced: true.state: {count:0
  },
  getters: {logCount:state= >'child-log-count:'+state.count
  },
  mutations:{
    addCount(state,payload){
      let offset = payload.count - 0 || 1
      state.count += offset
    },
    reduceCount(state,payload){
      let offset = payload.count - 0 || 1
      state.count -= offset
    },
    logCount(state){
      console.log(`child-mutations-log-${state.count}`)}},actions:{
    add({commit,dispatch,getters,rootState},payload){
      return new Promise((resolve,reject) = >{
        setTimeout((a)= > {
          if(Math.random() > 0.3){
            commit('addCount',payload);
            dispatch('add',payload,{ root: true });
            commit('logCount');
            resolve();
          }else{
            commit('logCount');
            reject('Add failed')}},300);
      })
    },
    reduce({commit,dispatch,getters,rootState},payload){
      return new Promise((resolve,reject) = >{
        setTimeout((a)= > {
          if(Math.random() > 0.3){
            commit('reduceCount',payload);
            dispatch('reduce',payload,{ root: true });
            commit('logCount');
            resolve();
          }else{
            reject('Add failed')}},1000); })}},modules: {grandchild: {namespaced: true.state: {count:0
      },
      getters: {logCount:state= >'grandchild-log-count:'+state.count
      },
      mutations:{
        addCount(state,payload){
          let offset = payload.count - 0 || 1
          state.count += offset
        },
        reduceCount(state,payload){
          let offset = payload.count - 0 || 1
          state.count -= offset
        },
        logCount(state){
          console.log(`grandchild-mutations-log-${state.count}`)}},actions:{
        add({commit,dispatch,getters,rootState},payload){
          return new Promise((resolve,reject) = >{
            setTimeout((a)= > {
              if(Math.random() > 0.3){
                commit('addCount',payload);
                dispatch('child/add',payload,{ root: true });
                commit('logCount');
                resolve();
              }else{
                commit('logCount');
                reject('Add failed')}},300);
          })
        },
        reduce({commit,dispatch,getters,rootState},payload){
          return new Promise((resolve,reject) = >{
            setTimeout((a)= > {
              if(Math.random() > 0.3){
                commit('reduceCount',payload);
                dispatch('child/reduce',payload,{ root: true });
                commit('logCount');
                resolve();
              }else{
                reject('Add failed')}},1000); })}},}}}const store = new Vuex.Store({
  namespaced: true.state: {count:0
  },
  getters: {logCount:state= >'parent-log-count:'+state.count
  },
  mutations:{
    addCount(state,payload){
      let offset = payload.count - 0 || 1
      state.count += offset
    },
    reduceCount(state,payload){
      let offset = payload.count - 0 || 1
      state.count -= offset
    },
    logCount(state){
      console.log(`parent-mutations-log-${state.count}`)}},actions:{
    add({commit,dispatch,getters,rootState},payload){
      return new Promise((resolve,reject) = >{
        setTimeout((a)= > {
          if(Math.random() > 0.3){
            commit('addCount',payload);
            commit('logCount');
            resolve();
          }else{
            reject('Add failed')}},1000);
      })
    },
    reduce({commit},payload){
      return new Promise((resolve,reject) = >{
        setTimeout((a)= > {
          if(Math.random() > 0.5){
            commit('reduceCount',payload);
            commit('logCount');
            resolve();
          }else{
            commit('logCount');
            reject('Add failed')}},1000); })}},modules:{
    child
  }
})
Copy the code
<template>
    <div>
        <p>demo9</p>
        <input type="number" name="" v-model="value" id="">
        <button @click='add'>add</button>
        <button @click='reduce'>reduce</button>
        
        <p>grandchildCount : {{grandchildCount}}</p>
        <p>{{grandchildLog}}</p>
        <p>childCount : {{childCount}}</p>
        <p>{{childLog}}</p>
        <p>parentCount : {{parentCount}}</p>
        <p>{{parentLog}}</p>
        
    </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
    data(){
        return{
            value:1}},computed: {... mapState({grandchildCount:state= >{
                return state.child.grandchild.count
            },
            childCount:state= >{
                return state.child.count
            },
            parentCount:state= >{
                returnstate.count } }), ... mapGetters({grandchildLog:'child/grandchild/logCount'.childLog:'child/logCount'.parentLog:'logCount'})},methods:{
        add(){
            this.$store.dispatch('child/grandchild/add', {count:this.value}).then((a)= >{
                console.log('Added successfully')}, () = > {console.log('Add failed')
            })
        },
        reduce(){
            this.$store.dispatch('child/grandchild/reduce', {count:this.value}).then((a)= >{
                console.log('Reduced success')}, () = > {console.log('Reduce failure')})},}}</script>
Copy the code

The project structure

Vuex doesn’t limit your code structure. However, it lays down some rules that need to be followed:

  • Application-level state should be centralized into a single Store object.
  • Submitting mutation is the only way to change the state, and the process is synchronous.
  • Asynchronous logic should be wrapped in actions. As long as you follow these rules, you can organize your code any way you want. If your store file is too large, just split the action, mutation, and getter into separate files.

For large applications, we would want to split Vuex related code into modules. Here is an example project structure:

├─ index.html ├─ download.js ├─ API │ ├─ ├─ ├─Extract THE API request├ ─ ─ components │ ├ ─ ─ App. Vue │ └ ─ ─... └ ─ ─ store ├ ─ ─ index. Js# where we assemble modules and export store├ ─ ─ actions. JsRoot level action├ ─ ─ mutations. JsMutation at the root level└ ─ ─ modules ├ ─ ─ cart. Js# Shopping cart module└ ─ ─ products. Js# Product module
Copy the code

Vue-state management process