# Actions

Les actions sont similaires aux mutations, à la différence que :

  • Au lieu de modifier l'état, les actions actent des mutations.
  • Les actions peuvent contenir des opérations asynchrones.

Enregistrons une simple action :

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

Les gestionnaires d'action reçoivent un objet contexte qui expose le même ensemble de méthodes et propriétés que l'instance du store, donc vous pouvez appeler context.commit pour acter une mutation, ou accéder à l'état et aux accesseurs via context.state et context.getters. Nous verrons pourquoi cet objet contexte n'est pas l'instance du store elle-même lorsque nous présenterons les Modules plus tard.

En pratique, nous utilisons souvent la déstructuration d'argument(opens new window) pour simplifier quelque peu le code (particulièrement si nous avons besoin d'appeler commit plusieurs fois) :

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

# Propager des actions

Les actions sont déclenchées par la méthode store.dispatch :

store.dispatch('increment')

Cela peut sembler idiot au premier abord : si nous avons besoin d'incrémenter le compteur, pourquoi ne pas simplement appeler store.commit('increment') directement ? Vous rappelez-vous que les mutations doivent être synchrones ? Les actions ne suivent pas cette règle. Il est possible de procéder à des opérations asynchrones dans une action :

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

Les actions prennent également en charge les paramètres additionnels (« payload ») et les objets pour propager :

// propager avec un paramètre additionnel
store.dispatch('incrementAsync', {
  amount: 10
})

// propager avec un objet
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

Un exemple concret d'application serait une action pour vider un panier d'achats, ce qui implique d'appeler une API asynchrone et d'acter de multiples mutations :

actions: {
  checkout ({ commit, state }, products) {
    // sauvegarder les articles actuellement dans le panier
    const savedCartItems = [...state.cart.added]
    // envoyer la requête de checkout,
    // et vider le panier
    commit(types.CHECKOUT_REQUEST)
    // l'API de la boutique en ligne prend une fonction de rappel en cas de succès et une autre en cas d'échec
    shop.buyProducts(
      products,
      // gérer le succès
      () => commit(types.CHECKOUT_SUCCESS),
      // gérer l'échec
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

Notez que nous procédons à un flux d'opérations asynchrones, et enregistrons les effets de bord (mutation de l'état) de l'action en les actant.

# Propager des actions dans les composants

Vous pouvez propager des actions dans les composants avec this.$store.dispatch('xxx'), ou en utilisant la fonction utilitaire mapActions qui attache les méthodes du composant aux appels de store.dispatch (nécessite l'injection de store à la racine) :

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment' // attacher `this.increment()` à `this.$store.dispatch('increment')`

      // `mapActions` supporte également les paramètres additionnels :
      'incrementBy' // attacher `this.incrementBy(amount)` à `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // attacher `this.add()` à `this.$store.dispatch('increment')`
    })
  }
}

# Composer les actions

Les actions sont souvent asynchrones, donc comment savoir lorsqu'une action est terminée ? Et plus important, comment composer plusieurs actions ensemble pour manipuler des flux asynchrones plus complexes ?

La première chose à savoir est que store.dispatch peut gérer la Promesse (« Promise ») retournée par le gestionnaire d'action déclenché et par conséquent vous pouvez également retourner une Promesse :

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

Maintenant vous pouvez faire :

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

Et également dans une autre action :

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

Pour finir, si nous utilisons async / await(opens new window) , nous pouvons composer nos actions ainsi :

// sachant que `getData()` et `getOtherData()` retournent des Promesses.

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // attendre que `actionA` soit finie
    commit('gotOtherData', await getOtherData())
  }
}

Il est possible pour un store.dispatch de déclencher plusieurs gestionnaires d'action dans différents modules. Dans ce genre de cas, la valeur retournée sera une Promesse qui se résout quand tous les gestionnaires déclenchés ont été résolus.