vuex - 即使不推荐,是否可以直接更改状态?

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

文档here 说,

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

我的问题是,这是好的做法,还是 Vuex 状态的内部工作原理?换句话说,Vuex 状态是否以与 Vue 数据相同的方式反应(它将 JS 对象转换为可观察对象),还是其他方式?

一个类似的问题 - 你能直接改变动作中的状态而不是创建突变吗?我知道这是不好的做法,它失去了一些遵循约定的可追溯性 - 但它有效吗?

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?

有效,但抛出警告和错误。

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)

谁知道这之后还有什么问题。

自己看(注意模板中的数据更新):

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?

是的。实际上,正是 Vue 本身使商店对象具有反应性。来自 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 }
      

因此,即使在突变代码中,如果您覆盖可观察对象或直接创建新属性(通过不调用 Vue.set(obj, 'newProp', newValue)),对象也不会反应。


跟进评论中的问题(好的!)

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?

他们可能是,但我不相信他们是。文档和证据(见下文 vm.$watch 讨论)指向它们与 data 对象完全相同,至少在 reaction/observable 行为方面。

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

这是个好问题。请允许我改写一下:

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?

答案就在within Store.commit()'s code. It executes the mutation code through a _withCommit() internal function.

所有this _withCommit() does is it sets a flag this._committing to true然后执行突变代码(执行后returns _committingfalse) .

然后 Vuex 存储 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

(奖励:do notice that vuex uses vm.$watch --see Vue's vm.$watch API docs 如果您不熟悉它——观察变量,状态对象与数据对象相同的另一个提示——它们依赖于 Vue 的内部结构。)

现在,为了证明我的观点,让我们 "trick" vuex 自己设置 state._committingtrue 然后调用 Vue.set() 来自突变体之外。如下所示,没有触发警告。 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>

我要让这个变得非常简单:

因为状态对象已经是反应式的,你可以完全避免使用getters和mutations。 Vue 的所有模板、computed、watch 等都将继续像使用组件数据一样工作。商店的状态充当共享数据对象。

但是这样做你将失去实现时间穿越调试、undo/redo和设置断点的能力,因为你已经绕过了command design pattern和成员的封装方法。 .