1. State

Vuex uses a single state tree — a single object that contains all application-level states. At this point it is the “Unique Data Source (SSOT)”. This also means that each app will contain only one Store instance.

Component to obtain the Vuex state

  • Calculate attribute

    import store from './store'
    const Counter = {
      template: `<div>{{ count }}</div>`.computed: {
        count () {
          return store.state.count
        }
      }
    }
    Copy the code

    However, components may need to import stores frequently.

  • Store options

    / / the parent component
    import store from './store'
    import Counter from './Counter'
    new Vue({
      el: '#app',
      store,	// "inject" store from the root component into each child component
      render:c= >c(Counter),
    })
    Copy the code
    // Subcomponent counter. vue can be accessed via 'this.$store' :<template>
    <div>{{count}}</div>
    </template>
    
    <script>
    export default {
        computed: {
        count () {
          return this.$store.state.count
        }
      }
    }
    </script>
    Copy the code

Auxiliary function mapState

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:

//store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex)

export default new Vuex.Store({
    state: {count:4}})Copy the code
//Counter.vue

<template>
    <div>{{sumCount}}</div>
</template>

<script>
import store from './store'
import {mapState} from 'vuex' // Introduce auxiliary functions
export default {
  data(){
    return{
      localCount:7}},computed:mapState({
        count:state= >state.count, // Return count () {return this.$store.state.count}
        countAlias:'count'.// The alias of count is countAlias, in which case countAlias equals count (note the single quotes).

        sumCount(state){ // Handle multiple states: store data plus local data
          return state.count+this.localCount
        }
  })

}
</script>
Copy the code

When the name of the computed attribute of the map is the same as the child node name of state, we can pass mapState an array of strings:

computed: mapState([
  'count'  Count :state=>state.count,
])
Copy the code

2. Getter

preface

Suppose we need to deal with state in a store, such as filtering and counting lists

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo= > todo.done).length
  }
}
Copy the code

However, if many components need the calculated property, we can only copy the function or import it frequently.

Vuex allows us to define “getters” in the Store. Just like evaluating properties, the return value of a getter is cached based on its dependency and recalculated only if its dependency value changes.

application

1. Parameters of the getter

  • getteracceptstateAs a parameter

    const store = new Vuex.Store({
      state: {
        todos: [{id: 1.text: '... '.done: true },
          { id: 2.text: '... '.done: false}},getters: {
        doneTodos: state= > {
          return state.todos.filter(todo= > todo.done)
        }
      }
    })
    Copy the code
  • getterAccept the second parametergetter

    getters: {
      doneTodos: state= > {
          return state.todos.filter(todo= > todo.done)
      }
      doneTodosCount: (state, getters) = > {
        return getters.doneTodos.length // Access the doneTodos property through the getter}}Copy the code

2. Access through attributes

/ / access
store.getters.doneTodos 
store.getters.doneTodosCount 
Copy the code
// used in the component
computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}
Copy the code

3. Access by method

The getter returns a function that takes parameters to the getter. This is useful for querying arrays in store

getters: {
  // ...
  getTodoById: (state) = > (id) = > {
    return state.todos.find(todo= > todo.id === id)
  }
}
Copy the code
/ / access
store.getters.getTodoById(2) // Return the element whose id is equal to the passed id
Copy the code

Helper function mapGetters

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // Mix the getter into a computed object using the object expansion operator. mapGetters(['doneTodosCount'.'anotherGetter'.// ...])}}Copy the code

3. Mutation

A simple example

The only way to change the state in Vuex’s store is to commit mutation. Mutation is similar to events: each mutation has an event type (type) and a callback function (handler). It takes state as the first argument:

const store = new Vuex.Store({
  state: {
    count: 1
  },
    
  mutations: {
    // Event type: increment; The callback function is the latter part;
    increment (state) {
      state.count++
    }
  }
})
Copy the code

To call a callback of this event type, use store.mit () and pass in the event type of the string

store.commit('increment')
Copy the code

Payload submission

Additional parameters can be passed to store.mit. These additional parameters are the payload of mutation.

mutations: {
  increment (state, n) {
    state.count += n
  }
}
Copy the code
store.commit('increment'.10)
Copy the code

In most cases, the payload should be an object, which is easier to read:

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
Copy the code
store.commit('increment', {
  amount: 10
})
Copy the code

submission

1. No load

store.commit('Event type')
Copy the code

2. Load:

//
store.commit('increment'.10)
Copy the code
// The payload is an object
store.commit('Event type', {
  amount: 10
})
Copy the code
// The payload is an object - object style
store.commit({
  type: 'Event type'.amount: 10
})
Copy the code

3. Commit in the component

this.$store.commit('Event type')
Copy the code

MapMutations auxiliary function

methods:{
    / /.... mapMutations(['increment'.$code.store.com MIT ('increment')
      'incrementBy'.Use this. Codestore.com MIT ('incrementBy', n) '
      'incrementBy2' $store.mit ('incrementBy2',{amount})
    ]),
    ...mapMutations({
      add:'increment' $store.com MIT ('increment')})}Copy the code

Note: Mutation must be a synchronization function

Use constants instead of Mutation types

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
Copy the code
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: {... },mutations: {
    // You can use ES2015 style computed attribute naming to use a constant as a function name
    [SOME_MUTATION] (state) {
      // mutate state}}})Copy the code

4. Action

preface

Action is similar to mutation, except that:

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

A simple example

// An instance of adding a random number to an array:
const store = new Vuex.Store({
  state: {
    msg: []},mutations: {
    addMessage(state,msg){
      state.msg.push(msg)
    }
  },
  
// An asynchronous operation:
actions: {
     getMessages(context){
       fetch('https://www.easy-mock.com/mock/5f4a32907e1a7f3146e313e7/api/getnum')
         .then(res= >res.json())
         .then(data= >{
           context.commit('addMessage',data.data.number)
         })       
     }
 }
})
Copy the code

The Action function accepts a Context object with the same methods and properties as the Store instance, so you can either get state and getters in this object or submit a mutation.

In ES6, you can simplify code with parameter deconstruction

actions: {
     getMessages({commit}){
       / /...
           commit('addMessage',data.data.number)
       / /...}}Copy the code

Distribution of the Action

1. General distribution

The Action is triggered by the store.dispatch method,

store.dispatch('getMessages')
Copy the code

2. Load distribution

/ / ordinary
store.dispatch('Event type', {
  amount: 1
})

// Object form
store.dispatch({
  type: 'Event type'.amount: 1
})
Copy the code

3. Distribute it to components

this.$store.dispatch('Event type')
Copy the code

MapMutations auxiliary function

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'getMessages'.// equivalent to 'this.$store.dispatch('getMessages')'
    ]),
    ...mapActions({
      addmsg: 'getMessages' //this.addmsg equals' this.$store.dispatch('getMessages') '}}})Copy the code

Combination of the Action

How do you know when an action is over? How can you combine multiple actions to handle more complex asynchronous processes? First thing to know:

  • store.dispatchCan handle the Promise returned by the handler of the triggered action
  • store.dispatchStill return Promise:
actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) = > {
      setTimeout(() = > {
        commit('someMutation')
        resolve()
      }, 1000)}}}Copy the code

Callbacks can now be specified:

store.dispatch('actionA').then(() = > {
  // ...
})
Copy the code

Another action can also specify the callback:

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() = > {
      commit('someOtherMutation')}}}Copy the code

Finally, with async/await, we can combine actions as follows:

actions: {
  async actionA ({ commit }) {
    commit('gotData'.await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // Wait for actionA to complete
    commit('gotOtherData'.await getOtherData())
  }
}
Copy the code

5. Module

preface

When the application becomes very complex, the Store object can become quite bloated. To solve this problem, Vuex allows us to split the Store into modules. Each module has its own state, mutation, action, getter, and even nested submodules:

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

Local state of a module

const moduleA = {
  state: () = > ({
    count: 0
  }),
    
  For mutation, the first parameter received is the module's local state object.
  mutations: {
    increment (state) {
      state.count++
    }
  },
  For getters, the first parameter exposes local state; The third parameter 'rootState' reveals the rootState
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
    
  //3. For action, the local state is exposed through context.state, and the root node is exposed through context.rootState:
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2= = =1) {
        commit('increment')}}}}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 add Namespaced: True to your module objects to make them namespaced. When a module is registered, all its getters, actions, and mutations are automatically named according to the path the module was registered with

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true.state: () = >({... }),getters: {isAdmin () { ... }},// Access from store.getters['account/isAdmin'
      actions: {login () { ... }},// Dispatch via store.dispatch('account/login')
      mutations: {login () { ... }},// Submit via store.mit ('account/login')

      // A nested module in a module - inherits the parent module's namespace
      modules: {
        1 - myPage / / module
        myPage: {
          state: () = >({... }),getters: {profile () { ... }}//store.getters['account/profile']
        },

        // module 2-mposts- further nested namespace
        posts: {
          namespaced: true.state: () = >({... }),getters: {popular () { ... }}//store.getters['account/posts/popular']}}}}})Copy the code

Access global content within a module with a namespace

RootState and rootGetters are passed into the getter as the third and fourth arguments, and the action is passed into 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.

modules: {
  foo: {
    namespaced: true.getters: {
      someOtherGetter: state= >{... }For the getter, use the fourth argument 'rootGetters' to access the root getter
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> local
        rootGetters.someOtherGetter // -> global}},actions: {
      someOtherAction (ctx, payload) { ... }
	  //2. For actions, accept the root attribute to access root Dispatch or commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        dispatch('someOtherAction') // -> Partial distribution
        dispatch('someOtherAction'.null, { root: true }) // -> Global distribution

        commit('someMutation') // -> Local commit
        commit('someMutation'.null, { root: true }) // -> Global commit}}}},Copy the code

Register global actions in namespaced modules

Add root: true to the action and put the action definition in a function handler. Such as:

{
  / /...
  modules: {
    foo: {
      namespaced: true.actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> define someAction action
        }
      }
    }
  }
}
Copy the code

A binding function with a namespace

When using functions such as mapState, mapGetters, mapActions, and mapMutations to bind a module with a namespace:

computed: { ... mapState({a: state= > state.some.nested.module.a,
    b: state= > state.some.nested.module.b
  })
},
methods: {
  ...mapActions([
    'some/nested/module/foo'.// -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()])}Copy the code

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 the context, simplifying:

computed: { ... mapState('some/nested/module', {
    a: state= > state.a,
    b: state= > state.b
  })
},
methods: {
  ...mapActions('some/nested/module'['foo'.// -> this.foo()
    'bar' // -> this.bar()])}Copy the code

Another way to simplify things: use createNamespacedHelpers to create namespace-based helper functions. It returns an object containing the new component binding helper function bound to the given namespace value:

import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')// Based on a name

export default {
  computed: {
    ...mapState({
      a: state= > state.a,
      b: state= > state.b
    })
  },
  methods: {
    ...mapActions([
      'foo'.'bar'])}}Copy the code

Example source code