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?
Question 1 : Pourquoi les développeurs de Vuejs ont-ils décidé de le faire de cette façon?
Répondre:
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:
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.
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.
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.
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:
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.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!
Les principales différences entre actions et mutations:
Selon le docs
Les actions ressemblent aux mutations , les différences étant que:
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
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
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.
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!
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à.
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.