更改组件后,Vuex存储中的数组中的项目会更新

时间:2020-05-30 00:40:01

标签: vue.js vuex

在我的Vuex store中有一系列的Notes。每个音符由<textarea>表示。我有一个NoteArray组件,可显示每个注释:

// NoteArray.vue
export default {
  name: "NoteArray",
  components: { Note },
  computed: {
    ...mapState({
      notes: state => state.notes // get array of Notes from store
    })
  },
  template: `
    <div v-for="note in notes">
      <!-- make one Note per note in array -->
      <Note :contents.sync="note.contents"></Note>
    </div>`
}
// Note.vue
export default {
  name: "Note",
  props: ["contents"], // recieve contents from NoteArray
  template: `<textarea v-model="contents"></textarea>`
}

如果我不使用Vuex,则此设置可能会正常工作,但我希望每个音符的内容由商店中的单个数组表示:

// index.ts
let store = new Vuex.Store({
  state: {
    notes: [{contents: ""}] // will have a mutation to add/remove notes
  }
}

现在我正在使用v-model将每个便笺的内容附加到其自身。对于单向绑定来说,这很好用-Notes的初始状态很好地向下传播。尝试更改便笺时出现问题。 sync modifier将在这里用于建立双向绑定,而无需定义任何输入事件。

Vuex并非如此-我只能使用突变来修改状态。启用严格模式后,以上示例将导致错误[vuex] do not mutate vuex store state outside mutation handlers

这里的解决方法是定义一个由@input上给定的Note调用的突变,该突变会更改该Note的contents的值。我能想到的唯一方法是定义一个访问内容并对其进行更改的突变(而不是v-modelsync):

// index.ts
...
  mutations: {
    update_note(state, payload) {
      state.notes[payload.index] = payload.context
    }
  }
...

...但是但这要求每个Note都知道并且能够将其自己在state.notes数组中的索引传递给突变。但是,每个注释完全不知道其上下文-它们没有此信息。

我不确定从这里去哪里-当用户更改了它们时,如何在contents中更新每个便笺的store的值?我希望NoteArray和Note保持它们自己的组件。

上述示例的实现:https://codesandbox.io/s/keen-moser-oe63d

2 个答案:

答案 0 :(得分:1)

经过一番挖掘,发现您要做的就是在v-model的{​​{1}}事件中抛弃$emit('update:contents', $event.target.value)@input。我最初的答案中包含的所有其他内容实际上并不需要。

这里是working example

如您所见,便笺在没有任何<textarea>的情况下进行了更新,并正确显示在commit中。我将测试放在App.vue中,以确保它们不仅在App.vue的{​​{1}}中处于更新状态,而且还处于更新状态。

我添加了唯一标识符是因为我发现,如果没有标识符,则删除注释vm时将显示错误的内容(来自notes数组中的下一个注释)。
这就是为什么要避免按索引键入的原因。 (请阅读this documentation section末尾的警告。)


现在,说句公道话,我真的不明白为什么通过NoteList.vue进行修改不会触发“请勿在商店外变异” 警告。要回答这个问题,就必须深入研究<textarea>的确切功能。或者也许与不更改对象的结构有关。不太确定。

无论如何,正确的做法是在.sync上分派一个操作,该操作将提交一个突变来更新商店:

示例:https://codesandbox.io/s/frosty-feather-lhurk?file=/src/components/NoteList.vue


另一注:如this discussion所示,.sync在状态属性之前没有习惯于“神奇地工作” ,因此它确实需要update:contents + prop.sync,显然不再需要。

答案 1 :(得分:1)

当将数组值(或任何复杂值)直接绑定到组件中时,这就是Vuex状态模式的经典问题。在Vue论坛(https://forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143)中将对此进行详细讨论。

基本上,这里的想法是仅存储具有ID的数组。这些ID然后引用一个对象,其中对象键是ID,而对象值是实际数据。如果像这样分解数据结构,则可以直接绑定到存储。主要目标是在商店中使用扁平结构,并且仅在其中存储具有ID的阵列。

在您的示例中,您实际上不需要数组,可以直接使用对象。然后看起来像这样:

商店:

dispatch

要直接绑定到商店,建议使用带有已定义的setter和getter的计算属性。这是一个示例:

commit

此值(注释)现在双向绑定到Vuex存储。它会自动刷新存储中的值。如果要在getter中返回一个对象,则可能应该首先创建该对象的副本(深克隆),否则您将直接对该对象进行突变。

有许多实用工具可将这种双向绑定行为映射到一个组件中(例如,参见https://vuex.vuejs.org/guide/state.html#the-mapstate-helper)。