Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vuex - is it possible to directly change state, even if not recommended?

Tags:

vue.js

vuex

The documentation here says,

You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations.

My question is, is that good practice, or that's how the internals of the Vuex state work? In other words, is the Vuex state reactive in the same way Vue data is (it converts the JS object to an observable), or is it something else?

A similar question - could you directly change the state in an action instead of creating a mutation? I know it's bad practice and it loses some of the traceability that following the conventions gives - but does it work?

like image 681
Yehosef Avatar asked Mar 06 '18 02:03

Yehosef


People also ask

Is Vuex state immutable?

As Redux is to React, Vuex is to Vue but the main difference is that Redux state is always immutable while Vuex uses mutations to change its state. Its granular reactivity system gives efficient updates and the well-structured design makes it easier to fetch, commit and compute derived data.

Which part of Vuex is responsible for directly making changes to the data store?

The second key defined in the store is the mutations key. As the name suggests, it contain an object of all of the properties responsible for making changes to the state .

Which property of a Vuex store exposes methods that can be used to change state?

Vuex Mutations In Vuex, the only way to modify a source state is through mutations. You might think of them as the methods property in a Vue instance, but serve to modify a state in a Vuex store. Additionally, mutations are carried out through the store, to ensure that changes are predictable.

What is strict mode in Vuex?

Strict mode runs a synchronous deep watcher on the state tree for detecting inappropriate mutations, and it can be quite expensive when you make large amount of mutations to the state. Make sure to turn it off in production to avoid the performance cost.


2 Answers

Could you directly change the state in an action instead of creating a mutation? I know it's bad practice and it loses some of the traceability that following the conventions gives - but does it work?

Works, but throws a warning AND an error.

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)

who knows what else might be broken after this.

See for yourself (notice the data updates in the template):

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>

the Vuex state reactive in the same way Vue data is (it converts the js object to an observable), or is it something else?

Yes. Actually, that's Vue itself that makes the store objects reactive. From the Mutations official docs:

Mutations Follow Vue's Reactivity Rules

Since a Vuex store's state is made reactive by Vue, when we mutate the state, Vue components observing the state will update automatically. This also means Vuex mutations are subject to the same reactivity caveats when working with plain Vue:

  1. Prefer initializing your store's initial state with all desired fields upfront.

  2. When adding new properties to an Object, you should either:

    • Use Vue.set(obj, 'newProp', 123), or

    • Replace that Object with a fresh one. For example, using the stage-3 object spread syntax we can write it like this:

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

So even within mutations code, if you overwrite observables or create new properties directly (by not calling Vue.set(obj, 'newProp', newValue)), the object won't be reactive.


Follow up questions from comments (good ones!)

So it seems the observable object is slightly different than the regular Vue data - changes are only allowed to happen from a mutation handler. Is that right?

They could be, but I don't believe they are. The docs and evidences (see below vm.$watch discussion below) point torward they being exactly the same as data objects, at least with regards to reaction/observable behaviors.

How does the object "know" it was mutated from a different context?

This is a good question. Allow me to rephrase it:

If calling Vue.set(object, 'prop', data); from within Vue throws an exception (see demo above), why calling Vue.set(object, 'prop', data); from within a mutation function doesn't?

The answer lies within Store.commit()'s code. It executes the mutation code through a _withCommit() internal function.

All that this _withCommit() does is it sets a flag this._committing to true and then executes the mutation code (and returns _committing to false after the exection).

The Vuex store is then watching the states' variables and if it notices (aka the watcher triggers) that the variable changed while the _committing flag was false it throws the warning.

(Bonus: do notice that vuex uses vm.$watch --see Vue's vm.$watch API docs if you are not familiar with it -- to observe the variables, another hint that state's objects are the same as data objects - they rely on Vue's internals.)

Now, to prove my point, let's "trick" vuex by setting state._committing to true ourselves and then call Vue.set() from outside a mutator. As you can see below, no warning is triggered. 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>
like image 120
acdcjunior Avatar answered Oct 27 '22 01:10

acdcjunior


I am going to make this very simple:

Because the state object is already reactive, you can completely avoid using getters and mutations. All of Vue’s templates, computed, watch, etc. will continue to work the same as if using a component’s data. The store’s state acts as a shared data object.

But by doing so you will lose the ability to implement time-travel debugging, undo/redo, and setting breakpoints, because you will have circumvented the command design pattern and encapsulation of a member by using methods.

like image 31
Steven Spungin Avatar answered Oct 26 '22 23:10

Steven Spungin