VueJS 3.4 — вычисленное значение можно получить из двух источников, но установить в одном

Логика моего приложения заключается в том, чтобы получить некоторый массив объектов из API с помощью @tanstack/vue-query, обновить некоторые из этих объектов и отправить запрос на исправление только для обновленных объектов.

Поэтому я создал пустой массив в магазине Pinia, который будет хранить обновленные объекты и в конце отправлять только эти объекты в запрос на исправление.

Попробовал несколько способов, и этот показался самым простым способом сделать это:

const store = useBlocksStore();
const { blocks } = useBlocksAPI();
const { activeBlock } = storeToRefs(store);

  const block = computed(
    {
      get() {
        const blockId = activeBlock.value ?? 0;
        if (store.blocks[blockId]) {
          return store.blocks[blockId];
        }
        return blocks.value[blockId];
      },
      set(value: Block) {
        const blockId = activeBlock.value ?? 0;
        store.$patch((state) => {
          state.blocks[blockId] = value;
        });
      },
    }
  );

<input v-model = "block.properties.left" type = "text" />

Итак, мое ожидаемое поведение - это getter, чтобы проверить, доступен ли блок с некоторым идентификатором в магазине, то есть он уже был изменен после получения из API, вернуть это значение, если не вернуть значение из API, а затем установщик всегда установите измененные значения в магазине.

Но вместо этого я получаю эту ошибку [Vue warn] Set operation on key "left" failed: target is readonly. Proxy(Object) {top: 25, bottom: 25, left: 0, right: 0}, так что, думаю, он пытается изменить значение из API.

Любая идея, чего мне здесь не хватает или чего я не понимаю, или какие-либо предложения по достижению этого с минимальным повторяющимся кодом, чтобы иметь возможность использовать это во многих других компонентах для обновления различных свойств блоков.

Песочница

Вы нигде не устанавливаете block.value. Вместо этого вы глубоко изменяете блок.value.properties.left, что является плохой практикой и не работает по причине, указанной в ошибке. Неизвестно, что такое Properties.left и почему он доступен только для чтения.

Estus Flask 30.06.2024 13:05

я не могу осмыслить v-модель:/я думал, что, поскольку геттер возвращает блок и, изменяя вложенное значение, установщик получит блок с измененным значением:/

pashata 30.06.2024 22:00

я не уверен, как подойти к этой проблеме, смогу ли я вообще решить ее с помощью одного помощника v-модели для всех значений в блочном объекте, или мне придется создать что-то составное, чтобы обрабатывать их все отдельно

pashata 30.06.2024 22:02

Пожалуйста, укажите stackoverflow.com/help/mcve и опишите, как именно вы хотите, чтобы это работало. Неизвестно, что это за объект {top: 25, bottom: 25, left: 0, right: 0} и почему он доступен только для чтения, эта проблема может возникнуть независимо от v-модели. «Я так и думал с тех пор» — так не работает. v-модель — это синтаксический сахар, который означает block.value.properties.left = $event.target.value на входе. Вы можете вызвать set() только в записи, вычисленной с помощью block.value = .... Поскольку вы уже используете pinia, вам, вероятно, потребуется добавить некоторую логику для хранения действий, а не разбрасывать ее по составным объектам.

Estus Flask 30.06.2024 22:47

Только что отредактировал свой пост со ссылкой на песочницу. Что ж, я пытаюсь создать некий «крючок» v-модели, который я могу повторно использовать в различных компонентах, которые будут обрабатывать блочный объект, который может быть вложен. Начальное значение блока должно поступать из конечной точки API, и когда некоторые данные блока изменяются, этот блок должен быть сохранен в хранилище. Итак, после того, как пользователь закончил модифицировать кучу разных блоков, я могу проверить хранилище Pinia и посмотреть, какие из них изменены, поэтому я отправлю запрос на исправление только для них, не обновляя все из ответа.

pashata 02.07.2024 08:39

Так что это только для чтения, потому что это данные из запроса vue, что имеет смысл. Вам нужно разобраться, как это должно работать в конечном итоге. Его необходимо клонировать в хранилище, чтобы сделать его доступным для записи, чего вы, вероятно, не сделали в этом вопросе. Обновляете ли вы данные из запроса в какой-то момент? Тогда должна быть логика для синхронизации хранилища с удаленными данными. Вероятно, вам понадобится параметризованный метод получения и действие в хранилище для обновления свойств из «properties». Да, может быть помощник для создания записываемого вычисления, подходящего для v-модели, но он будет таким же маленьким, как вызов геттера и действия.

Estus Flask 02.07.2024 11:30

Если бы в магазине был ответ с похожими вложенными данными stackoverflow.com/a/78693779/3731501, параметризованные геттеры были бы похожими. Что касается исправлений, я бы предложил сохранить копию исходных данных (вероятно, это «данные» из запроса, они не должны рассинхронизироваться с хранилищем) и использовать diff для данных хранилища перед их отправкой. Это приведет к тому, что «грязные» поля, значения которых не были изменены, не будут отправлены. Этот случай ничем не отличается от редактирования любой сложной формы: вы сохраняете состояние формы в локальном состоянии, а затем создаете diff-полезную нагрузку для запроса PATCH.

Estus Flask 02.07.2024 11:45

Ну, идея заключалась в том, чтобы данные из запроса не обновлялись до тех пор, пока вы не сохраните данные из хранилища, а когда вы сохраните данные из хранилища, чтобы очистить хранилище и обновить данные запроса, чтобы у меня всегда отображались последние данные. Вот почему в геттере в моем примере я проверяю, существуют ли данные для блока в хранилище, а затем использую их, если не просто использую данные из запроса. Я хочу избежать сохранения всех данных в pinia, которых может быть много, поскольку у vue-query уже есть собственный механизм кэширования.

pashata 02.07.2024 19:13

я проверю вопрос, который связан, спасибо! И да, это ничем не отличается от обработки какой-либо сложной формы с вложенными объектами... я хотел сохранить в хранилище только обновленные блоки, чтобы не перебирать их все при исправлении и поиске обновленных полей для исправления. Таким образом, я всегда буду обновлять все, что сохранено в Pinia.

pashata 02.07.2024 19:17

Если в запросе нет сотен или тысяч записей и не измерялась задержка, я бы не стал беспокоиться об оптимизации, оба подхода будут работать нормально. Выложил, вот песочница codeandbox.io/p/devbox/blocks-forked-q2mqxx

Estus Flask 05.07.2024 15:36

нет, максимум записей может быть от 1 до 200... Я просто почувствовал, что это неправильный подход, первоначальная идея показалась хорошим решением... я не могу открыть вам песочницу, думаю, она не установлена ​​в публичный доступ ... но я попробую применить ваш код в ответе... спасибо!

pashata 06.07.2024 01:31

Исходная идея неплохая, тем более, что состояние readonly уже есть и его нельзя видоизменить. Понятно, я исправил права доступа

Estus Flask 06.07.2024 01:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
12
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что block.properties.left — это данные запроса, доступные только для чтения, их необходимо клонировать, прежде чем их можно будет изменить. Вся логика управления данными магазина может быть перенесена в магазин. Вообще не рекомендуется напрямую мутировать данные из магазина в v-model без каких-либо действий, это тоже нужно учитывать.

Хранилище можно либо лениво заполнить данными, либо вернуться к данным из запроса, как это было опробовано в вопросе. Тот факт, что существует возможность изменения глубоко вложенных данных, предполагает, что для изменения полей блока должно быть отдельное действие:

export const useBlocksStore = defineStore("blocks", {
  state: () => ({
    _query: markRaw(useBlocksQuery()),
    blocks: {},
  }),
  getters: {
    getBlock() {
      return (id) => {
        // Avoid reactivity bugs by eagerly accessing data to track it
        const readonlyBlock = this._query.data.value[id];
        return this.blocks[id] ?? readonlyBlock;
      };
    },
  },
  actions: {
    updateBlock(id, value) {
      this.$patch((state) => {
        const blockData =
          state.blocks[id] ?? lodash.cloneDeep(state._query.data.value[id]);
        state.blocks[id] = lodash.merge(blockData, value);
      });
    },
    updateBlockField(id, path, value) {
      const state = this.$state;
      const blockData =
        state.blocks[id] ?? lodash.cloneDeep(state._query.data.value[id]);
      state.blocks[id] = lodash.set(blockData, path, value);
    },
  },
});

Или все данные запроса можно клонировать в хранилище. Для этого потребуется сравнить данные между хранилищем и запросом, прежде чем их можно будет отправить обратно в запросе PATCH, что является более прямым, но все же жизнеспособным подходом, который широко используется.

Имеет смысл дополнительно кэшировать значение параметризованных геттеров/вычисленное в вычисляемом компоненте:

const block = computed(() => store.getBlock(props.blockId));

А записываемые вычисления удобно использовать с v-model и собственными элементами:

const blockWidthModel = computed({
  get() {
    return block.value.dimensions.width;
  },
  set(value) {
    store.updateBlockField(props.blockId, "dimensions.width", value);
  },
});

Работает как шарм! Большое спасибо ! теперь с v-моделью все стало немного яснее :) Мне просто нужно выяснить, как сделать установщик типобезопасным, поскольку теперь мы передаем String в качестве пути, и это значение на самом деле не может быть проверено в updateBlockField , но оно огромное прогресс на данный момент!

pashata 06.07.2024 10:56

Пожалуйста. Это возможно в TS stackoverflow.com/questions/58434389 , также это можно использовать github.com/sindresorhus/type-fest/blob/main/source/paths.d.t‌​s . Вероятно, будет немного проще реализовать, если «путь» представляет собой массив ключей, а не строку, разделенную точками. IIRC есть сторонние типобезопасные альтернативы lodash.set, которые можно использовать для определения типа updateBlockField, но я не могу их запомнить.

Estus Flask 06.07.2024 11:06

Другие вопросы по теме