web-dev-qa-db-fra.com

vuex - est-il possible de changer directement d'état, même si cela n'est pas recommandé?

La documentation sur https://vuex.vuejs.org/en/getting-started.html dit,

Vous ne pouvez pas directement muter l'état du magasin. La seule façon de changer l'état d'un magasin est de valider explicitement les mutations.

Ma question est, est-ce une bonne pratique, ou c'est ainsi que fonctionnent les internes de l'état Vuex? En d'autres termes, l'état de Vuex est-il réactif de la même manière Vue sont (elles convertissent l'objet js en un observable), ou est-ce autre chose?

Une question similaire - pourriez-vous changer directement l'état d'une action au lieu de créer une mutation? Je sais que c'est une mauvaise pratique et elle perd une partie de la traçabilité que donne le respect des conventions - mais ça marche?

8
Yehosef

Pourriez-vous changer directement l'état d'une action au lieu de créer une mutation? Je sais que c'est une mauvaise pratique et elle perd une partie de la traçabilité que donne le respect des conventions - mais ça marche?

Fonctionne, mais lance un avertissement ET une erreur.

vue.js:584 [Vue warn]: Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] Do not mutate vuex store state outside mutation handlers."

   (found in <Component>)
   warn @ vue.js:584
   ...

vue.js:1719 Error: [vuex] Do not mutate vuex store state outside mutation handlers.
    at assert (VM260 vuex.js:103)

qui sait quoi d'autre pourrait être cassé après cela.

Voyez par vous-même (notez les mises à jour des données dans le modèle):

const store = new Vuex.Store({
strict: true,
  state: {
    people: []
  },
  mutations: {
    populate: function (state, data) {
      //Vue.set(state, 'people', data);
    }
  }
});
new Vue({
  store,
  el: '#app',
  mounted: function() {
    let self = this;
    this.$http.get('https://api.myjson.com/bins/g07qh').then(function (response) {
      // setting without commit
      Vue.set(self.$store.state, 'people', response.data); 
      //self.$store.commit('populate', response.data)
    }).catch(function (error) {
      console.dir(error);
    });
  },
  computed: {
    datadata: function() {
      return this.$store.state.people
    }
  },
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/vue-resource"></script>

<div id="app">
  Data: {{ datadata }}
</div>

l'état de Vuex est réactif de la même manière Vue sont (elles convertissent l'objet js en un observable), ou est-ce autre chose?

Oui. En fait, c'est Vue elle-même qui rend les objets du magasin réactifs. D'après les Documents officiels Mutations :

Les mutations suivent les règles de réactivité de Vue

Étant donné que l'état d'un magasin Vuex est rendu réactif par Vue, lorsque nous mutons l'état, Vue observant l'état seront mis à jour automatiquement. Cela signifie également que les mutations Vuex sont soumises aux mêmes avertissements de réactivité lorsque vous travaillez avec vue simple:

  1. Préférez initialiser l'état initial de votre magasin avec tous les champs souhaités à l'avance.

  2. Lorsque vous ajoutez de nouvelles propriétés à un objet, vous devez soit:

    • Utilisez Vue.set(obj, 'newProp', 123), ou

    • Remplacez cet objet par un nouveau. Par exemple, en utilisant le stage-3 syntaxe de propagation d'objet nous pouvons l'écrire comme ceci:

      state.obj = { ...state.obj, newProp: 123 }
      

Ainsi, même dans le code de mutations, si vous écrasez des observables ou créez directement de nouvelles propriétés (en n'appelant pas Vue.set(obj, 'newProp', newValue)), l'objet ne sera pas réactif.


Questions de suivi des commentaires (bonnes!)

Il semble donc que l'objet observable soit légèrement différent des données Vue normales - les modifications ne peuvent se produire qu'à partir d'un gestionnaire de mutation. Est-ce vrai?

Ils pourraient l'être, mais je ne le pense pas. Les documents et preuves (voir ci-dessous vm.$watch Discussion ci-dessous) indiquent qu'ils sont exactement les mêmes que les objets data, au moins en ce qui concerne les comportements de réaction/observables.

Comment l'objet "sait-il" qu'il a été muté dans un contexte différent?

C'est une bonne question. Permettez-moi de reformuler:

Si appeler Vue.set(object, 'prop', data); depuis l'intérieur Vue lève une exception (voir démo ci-dessus), pourquoi appeler Vue.set(object, 'prop', data); depuis une fonction de mutation ne fonctionne pas?

La réponse se trouve dans le code de Store.commit() . Il exécute le code de mutation via une fonction interne _withCommit() .

Tout ce que cette _withCommit() fait, elle définit un indicateur this._committing Sur true et puis exécute le code de mutation (et renvoie _committing à false après l'exection).

Le magasin Vuex est alors en regardant les variables des états et s'il remarque (aka les déclencheurs de l'observateur) que la variable a changé tandis que le _committing était false, il lance l'avertissement .

(Bonus: remarquez que vuex utilise vm.$watch --voir Documents de l'API vm.$watch De Vue si vous ne le connaissez pas - pour observez les variables, un autre indice que les objets d'état sont les mêmes que les objets de données - ils s'appuient sur les internes de Vue.)

Maintenant, pour prouver mon point de vue, "tour" vuex en réglant state._committing Sur true nous-mêmes, puis appelons Vue.set() de l'extérieur d'un mutateur. Comme vous pouvez le voir ci-dessous, aucun avertissement n'est déclenché. Touché.

const store = new Vuex.Store({
strict: true,
  state: {
    people: []
  },
  mutations: {
    populate: function (state, data) {
      //Vue.set(state, 'people', data);
    }
  }
});
new Vue({
  store,
  el: '#app',
  mounted: function() {
    let self = this;
    this.$http.get('https://api.myjson.com/bins/g07qh').then(function (response) {
      // trick the store to think we're using commit()
      self.$store._committing = true;
      // setting without commit
      Vue.set(self.$store.state, 'people', response.data); 
      // no warning! yay!
    }).catch(function (error) {
      console.dir(error);
    });
  },
  computed: {
    datadata: function() {
      return this.$store.state.people
    }
  },
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/vue-resource"></script>

<div id="app">
  Data: {{ datadata }}
</div>
13
acdcjunior

Je vais rendre cela très simple:

Étant donné que l'objet d'état est déjà reactive, vous pouvez complètement éviter d'utiliser des getters et des mutations. Tous les modèles, calculés, surveillés, etc. de Vue continueront de fonctionner de la même manière que si vous utilisiez les données d'un composant. Le state du magasin agit comme un shared data object.

Mais ce faisant, vous perdrez la possibilité d'implémenter le débogage dans le temps, l'annulation/la restauration et la définition de points d'arrêt, car vous aurez contourné le command design pattern et l'encapsulation d'un membre à l'aide de méthodes. https://en.m.wikipedia.org/wiki/Command_pattern

4
Steven Spungin