我的应用程序中的逻辑是使用
@tanstack/vue-query
从 API 获取一些对象数组,更新其中一些对象并仅针对更新的对象发送补丁请求。
因此,我在
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
检查具有某个ID的块是否在存储中可用,这意味着它在从API获取后已经被修改,如果不从API返回值则返回该值,然后setter 始终在商店中设置修改后的值。
但是我收到了这个错误
[Vue warn] Set operation on key "left" failed: target is readonly. Proxy(Object) {top: 25, bottom: 25, left: 0, right: 0}
,所以我猜它会尝试修改 API 中的值。
知道我在这里缺少或不理解什么,或者有任何建议以最少的重复代码来完成此操作,以便能够在许多其他组件中使用它来更新不同的块属性。
问题在于
block.properties.left
是来自查询的只读数据,需要先克隆它才能进行变异。管理商店数据的整个逻辑可以移至商店。不采取任何操作直接从存储中更改数据通常不是一个好的做法,这也需要解决。
存储可以惰性地填充数据并回退到查询中的数据,正如问题中所尝试的那样。存在更改深层嵌套数据的情况表明应该有一个单独的操作来更改块字段:
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
请求中发回,这是更直接但仍然广泛使用的可行方法。
额外缓存组件中计算的参数化 getters/计算值是有意义的:
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);
},
});