在我的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-model
和sync
):
// index.ts
...
mutations: {
update_note(state, payload) {
state.notes[payload.index] = payload.context
}
}
...
...但是但这要求每个Note都知道并且能够将其自己在state.notes
数组中的索引传递给突变。但是,每个注释完全不知道其上下文-它们没有此信息。
我不确定从这里去哪里-当用户更改了它们时,如何在contents
中更新每个便笺的store
的值?我希望NoteArray和Note保持它们自己的组件。
答案 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)。