What is Vuex?

Vuex is a state management mode that stores the common state of components and ensures state changes with corresponding rules.

Vuex core is a store, it was quite a container, it contains a state, action, mutation, getter, modules.

  • State: Used to store data and is the only data source in the store
  • Getters: Secondary wrapping based on state data, like computed properties in VUE, often used for filtering data and correlation calculation of multiple data
  • Mutation: similar to a function that is the only way to change state data and cannot be used to handle asynchronous events
  • Action: Similar to mutation, used to commit mutation to change state rather than directly change state, and can include arbitrary asynchronous operations
  • Modules: Similar to a namespace, used in a project to define and manipulate the state of modules separately for easy maintenance

Vuex features:

  1. The state of vuEX is reactive. If a Vue component reads state from state, the state changes or corresponds to the corresponding component.

  2. We cannot change the state directly. We must change the state by means of commit mutation.

Vuex process

The overall vuEX process is:

  1. Within the component, actions are distributed through Dispatches.
  2. Mutation is called using action
  3. A COMMIT within mutation is then triggered to modify the state
  4. Finally, the state changes, causing the page to render again.

Application scenarios of vuEX

  • In large applications, data for global sharing, such as global message alerts, control permissions, and so on.
  • Vuex can work with sessionStorage to store basic user information persistently.
  • Vuex can be used when data from multiple levels of components needs to be shared or when there is a causal relationship between data from multiple pages.

Vuex is not recommended for simple applications. In simple applications, you can use eventBus or other communication methods. To learn more about how other components communicate, see this article: Ways components communicate in VUE

Brief analysis of core concepts

state

Single state tree

Vuex uses a single state tree. Put all the states you need to save into a state object. Easy access.

Used in components

Vuex has a mechanism to inject store inside the root component, so that the store can be used within each component (called this.$store), requiring vue.use (vuex) globally.

//store.js
import Vuex from ('Vuex') Vue.use(Vuex) const store = new Vuex.Store({ state:{ count:1 }, action:{ // ... }, mutation:{ //... }})export default store


Copy the code
// app. vue root component import store from"./store.js"

const app = new Vue({
  el: '#app', // provide the store object with the "store" option, which injects the store instance into all the child store components, components: {Counter}, template: '<div class="app">
      <counter></counter>
    </div>
  `
})
Copy the code

Once declared in the root component, we can access the state through this.$store.state, typically injecting its value into computed

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

Copy the code

MapState helper function

In the case of using multiple states in a component, Vue provides an auxiliary function, MapState, to inject multiple states into computed, saving us some code.

So we can access the state through this

import { mapState } from 'vuex'

exportdefault { // ... Computed: mapState({// arrow function makes code more concise count: state => state.count, // Pass string parameters'count'Equivalent to 'state => state.count' countAlias:'count',

    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}
Copy the code

We can also pass mapState an array of strings when the name of the computed property of the map is the same as the name of the child node of State.

Computed: mapState([// map this.count to store.state.count'count'
])
Copy the code

Since the mapState function returns an object, you can use the object expander…

computed: {
  localComputed() {/ *... */}, // Use the object expansion operator to blend this object into an external object... mapState({ // ... })}Copy the code

Getter

A getter is equivalent to a computed property of a Vue component, and you can use it when you need to derive some properties (such as filtering) from state.

The Getter accepts state as its first argument:

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '... '.done: true },
      { id: 2, text: '... '.done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      returnState.todos.filter (todo => todo.done)}}}) //getter calls are called through store.getter, such as console.log(store.getter.donetodos)Copy the code

Getters can also accept other getters as second arguments:

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
store.getters.doneTodosCount // -> 1
Copy the code

In the component we call this.$store.getters

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}
Copy the code

MapGetter helper function

The mapGetters helper function simply maps the getters in the store to local computed properties that can be used within the component via this

Like mapState, mapGetter can be injected into computed through the object expansion operator

import { mapGetters } from 'vuex'

exportdefault { // ... Computed: {// Mix getters into a computed object using the object expansion operator... mapGetters(['doneTodosCount'.'anotherGetter', / /... ] )}}Copy the code

If you want to alias a getter, you can use object form

MapGetters ({/ / mapping ` enclosing doneCount ` for ` store. The getters. DoneTodosCount `doneCount: 'doneTodosCount'
})
Copy the code

mutation

The only way to change the state in Vuex’s store is to commit mutation.

Mutations in Vuex are very similar to events: each mutation has a string event type (type) and a callback function (handler). This callback is where we actually make the state change, and it takes state as the first argument:

const store = new Vuex.Store({ state: { count: 1 }, mutations: Increment (state) {// Change state state.count++}}}) // Call method: by committing it correspondingtype
store.commit('increment')
Copy the code

Additional parameters can be passed to mutation, that is, to submit the payload

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment'// In most cases, the payload is an object //... mutations: { increment (state, payload) { state.count += payload.amount } } store.commit('increment', {
  amount: 10
})

Copy the code

Object style submission

store.commit({
  type: 'increment', //mutation () amount: 10})Copy the code

As for the type of mutation events, constants are generally used to replace the type. In this way, multiple variables need to be modified when the type is renamed later, so as to prevent errors and improve development efficiency

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'const store = new Vuex.Store({ state: { ... Mutations: {// we can use ES2015 style computing attribute naming to use a constant as the function name [SOME_MUTATION] (state) {// mutate state}}})Copy the code

Mutation must be a synchronization function

Use mutation in the component

Used by injecting mutation into the methods attribute

You can also use the helper function mapMutation, as well as the object expander

import { mapMutations } from 'vuex'

exportdefault { // ... methods: { ... mapMutations(['increment', // Map 'this.increment()' to 'this.$store.commit('increment') // 'mapMutations' also supports payload:'incrementBy'// Map 'this.incrementBy(amount)' to 'this.$store.commit('incrementBy', amount)` ]), ... mapMutations({ add:'increment'// Map 'this.add()' to 'this.$store.commit('increment') `}}}Copy the code

Action

Action and mutation are similar, except that:

1. The action commits mutation and does not directly change the state

2. Action Supports asynchronous operations

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')}}})Copy the code

The Action function accepts a context object with the same methods and properties as the store instance, so you can submit a mutation by calling context.mit. Or get state and getters via context.state and context.getters.

In practice, the ES6 approach is usually used

actions: {
  increment ({ commit }) {
    commit('increment'}} //Action is triggered by the store.dispatch method: store.dispatch("increment")
Copy the code

Action supports both payload and object distribution

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

Distribute the Action in the component

You use this.$store.dispatch(‘ XXX ‘) to distribute actions in the component, or use the mapActions helper function to map the component’s methods to a store.dispatch call (which requires injecting store at the root node first) :

import { mapActions } from 'vuex'

exportdefault { // ... methods: { ... mapActions(['increment', // Map 'this.increment()' to 'this.$store.dispatch('increment') '//' mapActions' also supports payloads:'incrementBy'// Map 'this.incrementBy(amount)' to 'this.$store.dispatch('incrementBy', amount)` ]), ... mapActions({ add:'increment'// Map 'this.add()' to 'this.$store.dispatch('increment') `}}}Copy the code

Combination of the Action

// Asynchronous actions usually return a Promise action: {actionA ({commit}) {return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation'Resolve ()}, 1000)})} // Can now store. Dispatch ()'actionA').then(() => { // ... }) // In another action actions: {//... actionB ({ dispatch, commit }) {return dispatch('actionA').then(() => {
      commit('someOtherMutation')}}}Copy the code

If we use await and async, we can

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

module

In a large application, we often need to maintain many states. This makes our store very bloated and difficult to maintain

To solve these problems, Vuex allows us to split the Store into modules. Each module has its own state, mutation, action, and getter.

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 statusCopy the code
  • For mutation and getters inside a module, the first argument received is the module’s local state object.

  • For actions within the module, the local state is exposed through context.state and the rootState is context.rootstate

  • Also for getters inside the module, the root node state is exposed as the third parameter

const moduleA = { // ... Mutations: {count: 0}, mutations: {increment (state) {// the 'state' object is the local status of the module. Count ++}}, actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) {if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  },
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}
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.

But in practice, a program is often developed by several people, with different people responsible for different modules. This brings up a naming conflict problem, so in real development we need to use the Namespaced attribute to specify our respective namespaces to prevent naming conflicts.

After the namespace is used, the path to which the namespace needs to be added is invoked outside the namespace

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true, // Module assets state: {... }, // the state in the module is already nested, and using the 'namespaced' attribute doesn't affect it.isAdmin() {... } // -> getters['account/isAdmin']
      },
      actions: {
        login() {... } // -> dispatch('account/login')
      },
      mutations: {
        login() {... } // -> commit('account/login'}, // the nested module modules: {// inherits the parent module's namespace myPage: {state: {... }, getters: {profile() {... } // -> getters['account/profile'}}, // further nested namespace posts: {namespaced:true,

          state: { ... },
          getters: {
            popular() {... } // -> getters['account/posts/popular']}}}}}})Copy the code

To use a namespaced store in a component, precede it with its pathname

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