Обновить элемент в массиве в магазине Vuex при изменении компонента - PullRequest
0 голосов
/ 30 мая 2020

В моем 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 , чтобы установить sh двустороннюю привязку без необходимости определять какие-либо события ввода.

Не так для Vuex - я могу только изменить состояние с использованием мутации. При включенном строгом режиме приведенный выше пример приводит к ошибке [vuex] do not mutate vuex store state outside mutation handlers.

Исправление здесь состоит в том, чтобы определить мутацию, которая вызывается данной заметкой на @input, которая изменяет значение contents этой заметки. . Единственный способ, который я могу придумать, - это определить мутацию, которая обращается к содержимому и изменяет его (вместо v-model и sync):

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

... но для этого требуется, чтобы каждое примечание знает и может передать мутации свой индекс в массиве state.notes. Однако каждая заметка совершенно не осведомлена о своем контексте - у них нет этой информации.

Я не уверен, куда go отсюда - как я могу узнать значение contents каждой заметки обновляться в store, когда они меняются пользователем? Я хочу, чтобы NoteArray и Note оставались собственными компонентами.

Реализация приведенного выше примера: https://codesandbox.io/s/keen-moser-oe63d

Ответы [ 2 ]

1 голос
/ 30 мая 2020

Это классическая проблема паттерна состояний Vuex при привязке значений массива (или любых сложных значений) непосредственно к компонентам. Это подробно обсуждается на форуме Vue (https://forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143).

По сути, идея здесь состоит в том, чтобы хранить только массив с идентификаторами. Затем эти идентификаторы ссылаются на объект, где ключ объекта - это идентификатор, а значение объекта - это фактические данные. Если вы разберете структуру данных таким образом, вы сможете напрямую привязаться к хранилищу. Основная цель состоит в том, чтобы иметь плоские структуры в магазине и только массивы с идентификаторами в нем.

В вашем примере вам фактически не нужен массив, вы можете напрямую использовать объект. Тогда это будет выглядеть так:

Store:

const state = {
  notes: {
    1: {
      contents: 'test'
    },
    2: {
      contents: 'hi, I\'m a note.'
    }
  }
};

Для прямой привязки к хранилищу рекомендуется использовать вычисляемые свойства с определенными сеттерами и получателями. Вот пример:

props: {
  noteId: {
    type: Number,
    required: true
},
computed: {
  note: {
    get () {
      return this.$store.state.notes[this.noteId];
    },
    set (value) {
      this.$store.commit('setNote', {
        id: this.noteId,
        value: value
      });
    }
  }
}

Это значение (примечание) теперь двусторонне привязано к хранилищу Vuex. Он автоматически обновляет стоимость в магазине. Если вы возвращаете объект в геттере, вам, вероятно, сначала следует создать копию этого объекта (глубокое клонирование), иначе вы напрямую мутируете этот объект.

Существует множество служебных инструментов для сопоставления этих двух способ привязки к компоненту (см., например, https://vuex.vuejs.org/guide/state.html#the -mapstate-helper ).

1 голос
/ 30 мая 2020

После небольшого покопания оказывается, что все, что вам нужно сделать, это отбросить v-model и $emit('update:contents', $event.target.value) в событии @input из <textarea>. Все остальное, что содержится в моем первоначальном ответе, на самом деле не требуется.

Вот рабочий пример .

Как видите, заметки обновляются без commit, и они отображаются в App.vue правильно. Я поместил тест в App.vue, чтобы убедиться, что они обновляются в текущем состоянии, а не только в vm из NoteList.vue.

Я добавил уникальные идентификаторы, потому что обнаружил, что без них, когда удаление заметки <textarea> s приведет к отображению неправильного содержимого (из следующей заметки в массиве заметок).
Именно поэтому следует избегать ввода по индексу. (Прочтите предупреждение в конце этого раздела документации ).


Теперь, чтобы быть полностью справедливым, я действительно не понимаю, почему изменение через .sync не работает вызвать предупреждение «не изменять вне магазина» . Чтобы ответить на этот вопрос, нужно разобраться, что именно делает .sync. Или, может быть, это связано с тем, что структура объекта не меняется. Не совсем уверен.

В любом случае, правильный способ сделать это - отправить действие на update:contents, которое зафиксирует мутацию, которая обновит хранилище:

Пример: https://codesandbox.io/s/frosty-feather-lhurk?file= / src / components / NoteList. vue.


Другое примечание: как показано в этом обсуждении , prop.sync не использовались для «волшебной работы» из коробки перед свойствами состояния , поэтому ему действительно понадобились dispatch + commit, которые, по-видимому, больше не нужны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...