Логика моего приложения заключается в том, чтобы получить некоторый массив объектов из 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.
Любая идея, чего мне здесь не хватает или чего я не понимаю, или какие-либо предложения по достижению этого с минимальным повторяющимся кодом, чтобы иметь возможность использовать это во многих других компонентах для обновления различных свойств блоков.
я не могу осмыслить v-модель:/я думал, что, поскольку геттер возвращает блок и, изменяя вложенное значение, установщик получит блок с измененным значением:/
я не уверен, как подойти к этой проблеме, смогу ли я вообще решить ее с помощью одного помощника v-модели для всех значений в блочном объекте, или мне придется создать что-то составное, чтобы обрабатывать их все отдельно
Пожалуйста, укажите 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, вам, вероятно, потребуется добавить некоторую логику для хранения действий, а не разбрасывать ее по составным объектам.
Только что отредактировал свой пост со ссылкой на песочницу. Что ж, я пытаюсь создать некий «крючок» v-модели, который я могу повторно использовать в различных компонентах, которые будут обрабатывать блочный объект, который может быть вложен. Начальное значение блока должно поступать из конечной точки API, и когда некоторые данные блока изменяются, этот блок должен быть сохранен в хранилище. Итак, после того, как пользователь закончил модифицировать кучу разных блоков, я могу проверить хранилище Pinia и посмотреть, какие из них изменены, поэтому я отправлю запрос на исправление только для них, не обновляя все из ответа.
Так что это только для чтения, потому что это данные из запроса vue, что имеет смысл. Вам нужно разобраться, как это должно работать в конечном итоге. Его необходимо клонировать в хранилище, чтобы сделать его доступным для записи, чего вы, вероятно, не сделали в этом вопросе. Обновляете ли вы данные из запроса в какой-то момент? Тогда должна быть логика для синхронизации хранилища с удаленными данными. Вероятно, вам понадобится параметризованный метод получения и действие в хранилище для обновления свойств из «properties». Да, может быть помощник для создания записываемого вычисления, подходящего для v-модели, но он будет таким же маленьким, как вызов геттера и действия.
Если бы в магазине был ответ с похожими вложенными данными stackoverflow.com/a/78693779/3731501, параметризованные геттеры были бы похожими. Что касается исправлений, я бы предложил сохранить копию исходных данных (вероятно, это «данные» из запроса, они не должны рассинхронизироваться с хранилищем) и использовать diff для данных хранилища перед их отправкой. Это приведет к тому, что «грязные» поля, значения которых не были изменены, не будут отправлены. Этот случай ничем не отличается от редактирования любой сложной формы: вы сохраняете состояние формы в локальном состоянии, а затем создаете diff-полезную нагрузку для запроса PATCH.
Ну, идея заключалась в том, чтобы данные из запроса не обновлялись до тех пор, пока вы не сохраните данные из хранилища, а когда вы сохраните данные из хранилища, чтобы очистить хранилище и обновить данные запроса, чтобы у меня всегда отображались последние данные. Вот почему в геттере в моем примере я проверяю, существуют ли данные для блока в хранилище, а затем использую их, если не просто использую данные из запроса. Я хочу избежать сохранения всех данных в pinia, которых может быть много, поскольку у vue-query уже есть собственный механизм кэширования.
я проверю вопрос, который связан, спасибо! И да, это ничем не отличается от обработки какой-либо сложной формы с вложенными объектами... я хотел сохранить в хранилище только обновленные блоки, чтобы не перебирать их все при исправлении и поиске обновленных полей для исправления. Таким образом, я всегда буду обновлять все, что сохранено в Pinia.
Если в запросе нет сотен или тысяч записей и не измерялась задержка, я бы не стал беспокоиться об оптимизации, оба подхода будут работать нормально. Выложил, вот песочница codeandbox.io/p/devbox/blocks-forked-q2mqxx
нет, максимум записей может быть от 1 до 200... Я просто почувствовал, что это неправильный подход, первоначальная идея показалась хорошим решением... я не могу открыть вам песочницу, думаю, она не установлена в публичный доступ ... но я попробую применить ваш код в ответе... спасибо!
Исходная идея неплохая, тем более, что состояние readonly уже есть и его нельзя видоизменить. Понятно, я исправил права доступа
Проблема в том, что 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
, но оно огромное прогресс на данный момент!
Пожалуйста. Это возможно в TS stackoverflow.com/questions/58434389 , также это можно использовать github.com/sindresorhus/type-fest/blob/main/source/paths.d.ts . Вероятно, будет немного проще реализовать, если «путь» представляет собой массив ключей, а не строку, разделенную точками. IIRC есть сторонние типобезопасные альтернативы lodash.set, которые можно использовать для определения типа updateBlockField, но я не могу их запомнить.
Вы нигде не устанавливаете block.value. Вместо этого вы глубоко изменяете блок.value.properties.left, что является плохой практикой и не работает по причине, указанной в ошибке. Неизвестно, что такое Properties.left и почему он доступен только для чтения.