preface

At the heart of every Vuex application is the Store. A “store” is basically a container that contains most of the states in your app. Vuex differs from a purely global object in two ways:

  1. The state store for Vuex isresponsive. When the Vue component reads state from the store, if the state in the store changes, thenThe corresponding components will be updated efficiently accordingly.
  2. You can’t just change the state in the store. The only way to change the state in a store is to commit mutation explicitly. This allows us to easily track each state change, which allows us to implement tools that help us better understand our application.

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.

🌟 when to use

The simplicity of one-way data flow can easily be broken when our application encounters state shared by multiple components:

  1. Multiple viewsRely onIn the same state.
  2. Behavioral needs from different viewschangeSame state.

State

Single state tree – Contains all application-level states in a single object.

Get the Vuex state in the Vue component

Vuex’s state store is reactive, and the easiest way to read state from a Store instance is to return some state in a calculated property.

  1. Register at the root instancestoreOption, the store instance is injected into all children of the root component.
const app = new Vue({
  el: '#app'.// Provide the store object to the "store" option, which injects store instances into all child components
  store,
  components: { Counter },
  template: ` 
      
`
}) Copy the code
  1. Child components can pass throughthis.$storeAccess to state.
const Counter = {
  template: `<div>{{ count }}</div>`.computed: {
    count () {
      return this.$store.state.count
    }
  }
}
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.

Basic usage
// In the separately built version, the auxiliary function is vuex.mapstate
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // Arrow functions make code more concise
    count: state= > state.count,

    // Pass the string argument 'count' equal to 'state => state.count'
    countAlias: 'count'.// In order to be able to use 'this' to get local state, you must use regular functions
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}
Copy the code
String array

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
Object expansion operator

The mapState function returns an object.

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

Getters

  • When to use: Needs to be from state in storeDerive some states.
  • You can think of it as storeCalculate attribute.

Receive parameters

The Getter accepts state as its first argument, and may also accept other getters as its second argument.

  1. receivestateAs the first 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)
    },
    doneTodosCount: (state, getters) = > {
      return getters.doneTodos.length
    }
  }
})
Copy the code

access

Access by property

Getters are exposed as store.getters objects, and you can access these values as properties.

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}
Copy the code
Access by method
  • When to use: Need to givegetterThe refs.
  • It’s useful when you’re querying an array in a store.
getters: {
  // ...
  getTodoById: (state) = > (id) = > {
    return state.todos.find(todo= > todo.id === id)
  }
}
Copy the code
store.getters.getTodoById(2) // -> { id: 2, text: '... ', done: false }
Copy the code

When a getter is accessed through a method, it is called every time and does not cache the result.

MapGetters helper function

Map getters in stores to local computed properties.

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // Mix getters into a computed object using the object expansion operator. mapGetters(['doneTodosCount'.'anotherGetter'./ / the ` enclosing doneCount ` mapping for ` enclosing $store. Getters. DoneTodosCount `
      doneCount: 'doneTodosCount'])}}Copy the code

Mutations

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

  • Each mutation has a string ofEvent type (Type)And aCallback function (handler).
  • thisThe callback functionIs ourWhere the actual state change is madeAnd it will acceptstateAs aThe first parameter.
const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // Change the state
      state.count++
    }
  }
})
Copy the code

submission

Call the store.mit method
store.commit('increment')
Copy the code
Payload submission

Pass an additional parameter (payload) to store.mit:

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}
Copy the code
store.commit('increment'.10)
Copy the code
The payload should be an object
  • The payload should be an object so that it can contain multiple fields and the mutation recorded will be more readable.
// ...
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
Copy the code
store.commit('increment', {
  amount: 10
})
Copy the code
Object style submission

Use an object containing the type attribute directly:

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

Vue response rules need to be followed

Since the state in Vuex’s Store is responsive, the Vue component that monitors the state updates automatically when we change the state.

  1. It is best to initialize all required properties in store well in advance;
  2. Should be used when new attributes need to be added to an objectVue.set(obj, 'newProp', 123)Or replace an old object with a new one.
state.obj = { ... state.obj,newProp: 123 }
Copy the code

Replace Mutation event types with constants

// 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: {
    // We can use ES2015 style computed attribute naming to use a constant as the function name
    [SOME_MUTATION] (state) {
      // mutate state}}})Copy the code

🌟🌟 Mutation must be a synchronization function

Commit Mutation in the component

Two ways:

  1. Used in componentsthis.$store.commit('xxx')Submit mutation;
  2. usemapMutationsHelper functions are added to the componentmethodsMapped tostore.commitCall (requires store injection at the root node).
import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment'.// Map 'this.increment()' to 'this.store.com MIT ('increment')'

      // 'mapMutations' also supports payloads:
      'incrementBy' // Map 'this.incrementBy(amount)' to 'this. codestore.com MIT ('incrementBy', amount)'
    ]),
    ...mapMutations({
      add: 'increment' // Map 'this.add()' to 'this.store.mit ('increment')'}}})Copy the code

Actions

Difference from mutation

Action is similar to mutation, except that:

  1. Action submitsmutation, rather than directly changing the state;
  2. Actions can containArbitrary asynchronous operation.

Basic usage

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    // increment (context) {
    // context.commit('increment')
    // }
    increment ({ commit }) {
      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, we’ll often use ES2015’s parameter deconstruction to simplify code, especially if we need to call COMMIT many times.

Distribution of the Action

Triggered by the store.dispatch method
store.dispatch('increment')
Copy the code
🌟🌟 calls asynchronous apis and distributes multiple mutations
actions: {
  checkout ({ commit, state }, products) {
    // Back up the contents of the current shopping cart
    const savedCartItems = [...state.cart.added]
    // Issue a checkout request and optimistically empty the cart
    commit(types.CHECKOUT_REQUEST)
    // The shopping API accepts a success callback and a failure callback
    shop.buyProducts(
      products,
      // Successful operation
      () = > commit(types.CHECKOUT_SUCCESS),
      // Failed operation
      () = > commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}
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'

export default {
  // ...
  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
🌟🌟 store.dispatch still returnsPromise
actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) = > {
      setTimeout(() = > {
        commit('someMutation')
        resolve()
      }, 1000)}}}Copy the code
store.dispatch('actionA').then(() => {
  // ...
})
Copy the code
Use async/await
// Suppose getData() and getOtherData() return a Promise

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

Modules

Basic usage

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

Mutations and Actions accept parameters inside the module

  • For internal modulesmutationgetter, receivedThe first parameterIs the moduleLocal state object;
  • For internal modulesaction, the local state passescontext.stateWhen exposed, the root node status iscontext.rootState;
  • For internal modulesgetter.Root node StatusasThe third parameterExpose.
const moduleA = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  },
  // ...
  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 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. Such as:

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true.// Module assets
      state: () = >({... }),// Module states are already nested, and using the 'namespaced' attribute doesn't affect them
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      // Nested modules
      modules: {
        // Inherits the parent module's namespace
        myPage: {
          state: () = >({... }),getters: {
            profile () { ... } // -> getters['account/profile']}},// Further nested namespaces
        posts: {
          namespaced: true.state: () = >({... }),getters: {
            popular () { ... } // -> getters['account/posts/popular']}}}}}})Copy the code
Access global content within a module with a 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.

modules: {
  foo: {
    namespaced: true.getters: {
      // In the getter of this module, 'getters' is localized
      // You can call 'rootGetters' with the fourth argument in the getter
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state= >{... }},actions: {
      // In this module, dispatch and commit are also localized
      // They can accept the 'root' attribute to access root dispatch or commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction'.null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation'.null, { root: true }) // -> 'someMutation'}, someOtherAction (ctx, payload) { ... }}}}Copy the code
Register global actions in namespaced modules

To register a global action in a namespaced module, you can add root: true and put the action definition in a function handler:

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')}},modules: {
    foo: {
      namespaced: true.actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}
Copy the code
🌟🌟 a binding function with a namespace

When using the mapState, mapGetters, mapActions, and mapMutations functions to bind a module with a namespace, 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:

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
Create helper functions based on a namespace

You can create helper functions based on a namespace by using createNamespacedHelpers. 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')

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

The project structure

The rules

  1. Application-level state should be centralized into a single Store object;
  2. submitmutationThe only way to change the stateAnd the process issynchronous;
  3. Asynchronous logicShould be encapsulated intoactionThe inside.

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

Use Vuex Vuex4.0 | Vue3.0

createStore

Create a store instance using createStore:

import { createApp } from 'vue'
import { createStore } from 'vuex'

// Create a new store instance.
const store = createStore({
  state () {
    return {
      count: 0}},mutations: {
    increment (state) {
      state.count++
    }
  }
})

const app = createApp({ /* your root component */ })

// Install the store instance as a plugin
app.use(store)
Copy the code

useStore

To get the store example in setup, you can call the useStore method, equivalent to this.$store in vue2.x:

import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()
  }
}
Copy the code

The State and Getters

Using computed to return a state:

import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // access a state in computed function
      count: computed(() = > store.state.count),

      // access a getter in computed function
      double: computed(() = > store.getters.double)
    }
  }
}
Copy the code

Mutations and Actions

import { useStore } from 'vuex'

export default {
  setup () {
    const store = useStore()

    return {
      // access a mutation
      increment: () = > store.commit('increment'),

      // access an action
      asyncIncrement: () = > store.dispatch('asyncIncrement')}}}Copy the code

The resources

  • Vuex website
  • Shopping cart example
  • Composition API | Vuex4.0

Interview question reference

Vuex Interview questions summary