web-dev-qa-db-fra.com

Vuex Action vs Mutations

Dans Vuex, quelle est la logique d'avoir à la fois des "actions" et des "mutations?"

Je comprends la logique des composants ne pouvant pas modifier l’état (ce qui semble intelligent), mais le fait d’avoir à la fois des actions et des mutations semble donner l’impression d’une fonction pour déclencher une autre fonction, puis modifier l’état.

Quelle est la différence entre "actions" et "mutations", comment fonctionnent-elles ensemble, et plus encore, je suis curieux de savoir pourquoi les développeurs Vuex ont décidé de le faire de cette façon?

108
Kobi

Question 1 : Pourquoi les développeurs de Vuejs ont-ils décidé de le faire de cette façon?

Répondre:

  1. Lorsque votre application devient volumineuse et que plusieurs développeurs travaillent sur ce projet, la "gestion de l'état" (en particulier "l'état global") devient de plus en plus compliquée.
  2. La méthode vuex (comme Redux dans react.js ) offre un nouveau mécanisme pour gérer l’état, conserver l’état et "enregistrer et suivre" (c’est-à-dire que toute action modifiant l’état peut être suivie par - outil de débogage: vue-devtools )

Question 2 : Quelle est la différence entre "action" et "mutation"?

Voyons d'abord l'explication officielle:

Mutations:

Les mutations Vuex sont essentiellement des événements: chaque mutation a un nom et un gestionnaire.

import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    INCREMENT (state) {
      // mutate state
      state.count++
    }
  }
})

Actions: Les actions ne sont que des fonctions qui envoient des mutations.

// the simplest action
function increment (store) {
  store.dispatch('INCREMENT')
}

// a action with additional arguments
// with ES2015 argument destructuring
function incrementBy ({ dispatch }, amount) {
  dispatch('INCREMENT', amount)
}

Voici mon explication de ce qui précède:

  • mutation est le seul moyen de modifier l'état
  • mutation ne se soucie pas de la logique métier, mais de "l'état"
  • action est une logique métier
  • action peut envoyer plus d'une mutation à la fois, il ne fait que mettre en œuvre la logique métier, il se moque de la modification des données (qui gèrent par mutation)
155
Kaicui

Les mutations sont synchrones, alors que les actions peuvent être asynchrones.

En d'autres termes: vous n'avez pas besoin d'actions si vos opérations sont synchrones, sinon implémentez-les.

47
Bas

Je pense que la réponse TLDR est que les mutations sont censées être synchrones/transactionnelles. Ainsi, si vous devez exécuter un appel Ajax ou tout autre code asynchrone, vous devez le faire dans une Action, puis valider une mutation après, pour définir le nouvel état.

13
Noah Namey

Je crois que comprendre les motivations de Mutations and Actions permet de mieux juger quand utiliser quoi et comment. Cela libère également le programmeur du fardeau de l'incertitude dans les situations où les "règles" deviennent floues. Après avoir un peu réfléchi à leurs objectifs respectifs, je suis parvenu à la conclusion que, même s'il pouvait exister des fausses façons d'utiliser Actions et Mutations, je ne pensais pas qu'il y avait une approche canonique.

Essayons d'abord de comprendre pourquoi nous passons même par des mutations ou des actions.

Pourquoi passer par le passe-partout en premier lieu? Pourquoi ne pas changer d'état directement dans les composants?

Strictement parlant, vous pouvez changer le state directement à partir de vos composants. La state n'est qu'un objet JavaScript et rien de magique ne permet de revenir sur les modifications que vous y apportez.

// Yes, you can!
this.$store.state['products'].Push(product)

Cependant, en faisant cela, vous dispersez vos mutations d'état dans tous les sens. Vous perdez la possibilité d'ouvrir simplement un seul module contenant l'état et de voir d'un coup d'œil le type d'opérations pouvant être appliqué. Avoir des mutations centralisées résout ce problème, bien qu’au prix de quelques-uns.

// so we go from this
this.$store.state['products'].Push(product)

// to this
this.$store.commit('addProduct', {product})

...
// and in store
addProduct(state, {product}){
    state.products.Push(product)
}
...

Je pense que si vous remplacez quelque chose de court par un passe-partout, vous voudrez que ce dernier soit également petit. Je présume donc que les mutations sont censées être des enveloppes très minces autour d'opérations natives sur l'état, avec presque aucune logique métier. En d'autres termes, les mutations sont destinées à être principalement utilisées comme des setters.

Maintenant que vous avez centralisé vos mutations, vous avez une meilleure vue d'ensemble de vos changements d'état et, étant donné que votre outil (vue-devtools) est également conscient de l'emplacement, il facilite le débogage. Il convient également de garder à l'esprit que de nombreux plugins de Vuex ne surveillent pas directement l'état pour suivre les changements, ils s'appuient plutôt sur des mutations pour cela. Les modifications "hors limites" de l'état leur sont donc invisibles.

Alors, mutations, actions quelle est la différence de toute façon?

Les actions, comme les mutations, résident également dans le module du magasin et peuvent recevoir l'objet state. Ce qui implique qu'ils pourraient le transforment également directement. Alors, quel est le point d'avoir les deux? Si nous estimons que les mutations doivent rester petites et simples, cela signifie que nous avons besoin d'un autre moyen pour héberger une logique métier plus élaborée. Les actions sont le moyen de le faire. Et comme nous l'avons établi précédemment, Vue-devtools et les plugins sont conscients des changements apportés par les mutations. Pour rester cohérents, nous devons continuer à utiliser les mutations de nos actions. De plus, étant donné que les actions sont censées être toutes englobantes et que la logique qu'elles englobent peut être asynchrone, il est logique que Actions devienne également simplement asynchrone dès le début.

Il est souvent souligné que les actions peuvent être asynchrones, alors que les mutations ne le sont généralement pas. Vous pouvez décider de voir la distinction comme une indication que les mutations doivent être utilisées pour tout ce qui est synchrone (et les actions pour tout ce qui est asynchrone); Cependant, vous rencontreriez des difficultés si, par exemple, vous deviez commettre plusieurs mutations (de manière synchrone) ou si vous deviez travailler avec un Getter de vos mutations, les fonctions de mutation ne recevant ni arguments de Getters ni mutations ...

... ce qui conduit à une question intéressante.

Pourquoi les mutations ne reçoivent-elles pas de Getters?

Je n'ai pas encore trouvé de réponse satisfaisante à cette question. J'ai vu des explications de la part de l'équipe principale que je trouvais au mieux discutable. Si je résume leur utilisation, les Getters sont censés être des extensions calculées de l'état (et souvent mises en cache). En d'autres termes, ils sont fondamentalement toujours l'État, bien que cela nécessite des calculs initiaux et qu'ils soient normalement en lecture seule. C'est au moins comment ils sont encouragés à être utilisés.

Ainsi, pour empêcher Mutations d'accéder directement à Getters, l'une des trois choses suivantes est désormais nécessaire, si nous devons accéder à partir de la première à certaines fonctionnalités offertes par cette dernière: (1) soit les calculs d'état fournis par Getter sont dupliqués dans un endroit accessible à la Mutation (mauvaise odeur), ou (2) la valeur calculée (ou le Getter pertinent lui-même) est transmise en tant qu'argument explicite à la Mutation (funky), ou (3) la logique du Getter lui-même est dupliquée directement dans la Mutation , sans le bénéfice supplémentaire de la mise en cache fourni par Getter (puanteur).

Voici un exemple de (2), qui, dans la plupart des scénarios que j'ai rencontrés, semble être l'option "la moins mauvaise".

state:{
    shoppingCart: {
        products: []
    }
},

getters:{
    hasProduct(state){
        return function(product) { ... }
    }
}

actions: {
    addProduct({state, getters, commit, dispatch}, {product}){

        // all kinds of business logic goes here

        // then pull out some computed state
        const hasProduct = getters.hasProduct(product)
        // and pass it to the mutation
        commit('addProduct', {product, hasProduct})
    }
}

mutations: {
    addProduct(state, {product, hasProduct}){ 
        if (hasProduct){
            // mutate the state one way
        } else {
            // mutate the state another way 
        }
    }
}

Pour moi, ce qui précède semble non seulement un peu compliqué, mais aussi un peu "fuyant", car une partie du code présent dans l'Action sort clairement de la logique interne de la Mutation.

À mon avis, cela indique un compromis. Je crois que permettre à Mutations de recevoir automatiquement Getters présente certains défis. Il peut s'agir soit de la conception de Vuex elle-même, soit de l'outillage (vue-devtools et al), soit de maintenir une compatibilité en amont, ou une combinaison de toutes les possibilités indiquées.

Ce que je ne crois pas, c'est que passer Getters à vos mutations vous-même est nécessairement un signe que vous faites quelque chose de mal. Je le vois simplement comme une "correction" d'une des faiblesses du framework.

12
Michael Ekoka

Clause de non-responsabilité - Je viens tout juste de commencer à utiliser Vuejs, c'est donc une extrapolation de l'intention de conception.

Le débogage de la machine temporelle utilise des instantanés de l'état et affiche une chronologie des actions et des mutations. En théorie, nous aurions pu avoir simplement actions à côté d'un enregistrement de régleurs d'état et de getters pour décrire la mutation de manière synchrone. Mais alors:

  • Nous aurions des entrées impures (résultats asynchrones) qui causaient les setters et les getters. Ce serait difficile à suivre logiquement et différents systèmes de réglage async et getters peuvent interagir de manière surprenante. Cela peut toujours arriver avec les transactions mutations, mais on peut alors affirmer que la transaction doit être améliorée, par opposition à une condition de concurrence critique dans les actions. Les mutations anonymes au sein d'une action pourraient plus facilement refaire surface de ce type de bugs car la programmation asynchrone est fragile et difficile.
  • Le journal des transactions serait difficile à lire car il n'y aurait pas de nom pour les changements d'état. Ce serait beaucoup plus semblable à un code et moins anglais, sans les regroupements logiques de mutations.
  • Il peut s'avérer plus délicat et moins performant d’enregistrer toutes les mutations d’un objet de données sur un instrument, contrairement à l’instant où il existe des points de différence définis de manière synchrone - avant et après l’appel de la fonction de mutation. Je ne suis pas sûr de l'ampleur du problème.

Comparez le journal de transactions suivant avec les mutations nommées.

Action: FetchNewsStories
Mutation: SetFetchingNewsStories
Action: FetchNewsStories [continuation]
Mutation: DoneFetchingNewsStories([...])

Avec un journal de transactions qui n'a pas de mutations nommées:

Action: FetchNewsStories
Mutation: state.isFetching = true;
Action: FetchNewsStories [continuation]
Mutation: state.isFetching = false;
Mutation: state.listOfStories = [...]

J'espère que vous pourrez extrapoler à partir de cet exemple la complexité supplémentaire potentielle des mutations asynchrones et anonymes au sein des actions.

https://vuex.vuejs.org/en/mutations.html

Maintenant, imaginons que nous déboguons l'application et examinions les journaux de mutation de devtool. Pour chaque mutation enregistrée, l'outil de développement devra capturer un instantané "avant" et "après" de l'état. Cependant, le rappel asynchrone à l'intérieur de l'exemple de mutation ci-dessus rend cela impossible: le rappel n'est pas encore appelé lorsque la mutation est validée et il n'y a aucun moyen pour le devtool de savoir quand le rappel sera réellement appelé - aucune mutation d'état effectuée dans le rappel est essentiellement non traçable!

5
ubershmekel

Les principales différences entre actions et mutations:

  1. Dans les actions, vous pouvez exécuter du code asynchrone, mais pas dans les mutations. Utilisez donc des actions pour le code asynchrone, sinon utilisez des mutations.
  2. Dans les actions, vous pouvez accéder aux accesseurs, à l'état, aux mutations (les commettre), aux actions (les distribuer) dans les mutations, vous pouvez accéder à l'état. Donc, si vous voulez accéder uniquement aux mutations d'état, utilisez des actions.
5
roli roli

Selon le docs

Les actions ressemblent aux mutations , les différences étant que:

  • Au lieu de muter l'état, actions commit mutations.
  • Les actions peuvent contenir des opérations arbitraires .

Considérez l'extrait suivant.

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++               //Mutating the state. Must be synchronous
    }
  },
  actions: {
    increment (context) {
      context.commit('increment') //Committing the mutations. Can be asynchronous.
    }
  }
})

Les gestionnaires d’action ( increment ) reçoivent un objet de contexte qui expose le même ensemble de méthodes/propriétés sur l’instance de magasin. Vous pouvez donc appeler context.commit pour valider. une mutation, ou accéder à l'état et aux accesseurs via context.state et context.getters

5
Abdullah Khan

Cela m'a aussi perturbé alors j'ai fait une simple démo.

composant.vue

<template>
    <div id="app">
        <h6>Logging with Action vs Mutation</h6>
        <p>{{count}}</p>
        <p>
            <button @click="mutateCountWithAsyncDelay()">Mutate Count directly with delay</button>
        </p>
        <p>
            <button @click="updateCountViaAsyncAction()">Update Count via action, but with delay</button>
        </p>
        <p>Note that when the mutation handles the asynchronous action, the "log" in console is broken.</p>
        <p>When mutations are separated to only update data while the action handles the asynchronous business
            logic, the log works the log works</p>
    </div>
</template>

<script>

        export default {
                name: 'app',

                methods: {

                        //WRONG
                        mutateCountWithAsyncDelay(){
                                this.$store.commit('mutateCountWithAsyncDelay');
                        },

                        //RIGHT
                        updateCountViaAsyncAction(){
                                this.$store.dispatch('updateCountAsync')
                        }
                },

                computed: {
                        count: function(){
                                return this.$store.state.count;
                        },
                }

        }
</script>

store.js

import 'es6-promise/auto'
import Vuex from 'vuex'
import Vue from 'vue';

Vue.use(Vuex);

const myStore = new Vuex.Store({
    state: {
        count: 0,
    },
    mutations: {

        //The WRONG way
        mutateCountWithAsyncDelay (state) {
            var log1;
            var log2;

            //Capture Before Value
            log1 = state.count;

            //Simulate delay from a fetch or something
            setTimeout(() => {
                state.count++
            }, 1000);

            //Capture After Value
            log2 = state.count;

            //Async in mutation screws up the log
            console.log(`Starting Count: ${log1}`); //NRHG
            console.log(`Ending Count: ${log2}`); //NRHG
        },

        //The RIGHT way
        mutateCount (state) {
            var log1;
            var log2;

            //Capture Before Value
            log1 = state.count;

            //Mutation does nothing but update data
            state.count++;

            //Capture After Value
            log2 = state.count;

            //Changes logged correctly
            console.log(`Starting Count: ${log1}`); //NRHG
            console.log(`Ending Count: ${log2}`); //NRHG
        }
    },

    actions: {

        //This action performs its async work then commits the RIGHT mutation
        updateCountAsync(context){
            setTimeout(() => {
                context.commit('mutateCount');
            }, 1000);
        }
    },
});

export default myStore;

Après des recherches, la conclusion à laquelle je suis arrivé est que les mutations sont une convention centrée uniquement sur la modification des données afin de mieux séparer les préoccupations et d'améliorer la journalisation avant et après les données mises à jour. Considérant que les actions sont une couche d'abstraction qui gère la logique de niveau supérieur et appelle ensuite les mutations de manière appropriée

2
Nathaniel Rink

Mutations:

Can update the state. (Having the Authorization to change the state).

Actions:

Actions are used to tell "which mutation should be triggered"

À la manière de Redux

Mutations are Reducers
Actions are Actions

Pourquoi les deux ??

Quand l'application grandira, le codage et les lignes augmenteront. Cette fois, vous devrez gérer la logique dans Actions non dans les mutations, car les mutations sont la seule autorité pour modifier l'état. Il doit être aussi propre que possible.

1
Gopinath Kaliappan

Parce qu’il n’ya pas d’état sans mutations! Lorsqu'elle est validée, un élément de logique, qui modifie l'état de manière prévisible, est exécuté. Les mutations sont le seul moyen de définir ou de modifier l’état (il n’ya donc pas de changements directs!), Mais elles doivent également être synchrones. Cette solution entraîne une fonctionnalité très importante: les mutations se connectent à devtools. Et cela vous offre une grande lisibilité et prévisibilité!

Une dernière chose - les actions. Comme il a été dit - les actions provoquent des mutations. Donc, ils ne changent pas le magasin, et il n’est pas nécessaire que ceux-ci soient synchrones. Mais, ils peuvent gérer un morceau supplémentaire de logique asynchrone!

0
Sumit Patel

1.De docs :

Les actions sont similaires aux mutations, les différences étant que:

  • Au lieu de muter l'État, les actions commettent des mutations.
  • Les actions peuvent contenir des opérations asynchrones arbitraires.

Les actions peuvent contenir des opérations asynchrones, mais la mutation ne le peut pas.

2.Nous invoquons la mutation, nous pouvons changer l'état directement. et nous pouvons aussi dans l'action pour changer d'états comme ceci:

actions: {
  increment (store) {
    // do whatever ... then change the state
    store.dispatch('MUTATION_NAME')
  }
}

les actions sont conçues pour gérer plus de choses, on peut faire beaucoup de choses dedans (on peut utiliser des opérations asynchrones) puis changer d'état par mutation de mutation là.

0
aircraft

Il peut sembler inutile d’avoir une couche supplémentaire de actions juste pour appeler le mutations, par exemple:

const actions = {
  logout: ({ commit }) => {
    commit("setToken", null);
  }
};

const mutations = {
  setToken: (state, token) => {
    state.token = token;
  }
};

Donc, si vous appelez actions appelle logout, pourquoi ne pas appeler la mutation elle-même?

L'idée même d'une action est d'appeler plusieurs mutations à partir d'une action ou de faire une requête Ajax ou tout type de logique asynchrone imaginable.

Nous pourrions éventuellement avoir des actions qui font plusieurs demandes de réseau et éventuellement appeler plusieurs mutations différentes.

Nous essayons donc d’intégrer autant de complexité que possible de notre Vuex.Store() dans notre actions et cela laisse nos mutations, state et getters plus propres et s’adapte à la type de modularité qui rend populaires des bibliothèques comme Vue et React.

0
Daniel