我一直在试用带有类型记录的Immer.js,但我经常遇到类似的问题。有什么模式可以用来避免一些丑陋的代码吗?
请考虑下列打字本代码:
import produce, {castDraft} from "immer";
interface Item {
readonly id: number;
readonly value?: string;
}
interface State {
readonly items: Item[];
}
function findItemById(state: State, id: number): Item {
return state.items.find(x => x.id === id)!;
}
const state: State = {
items: [{ id: 1 }, { id: 2 }, { id: 3 }]
};
// This does not work.
produce(state, draft => {
const item = findItemById(draft, 2);
// Cannot assign to "value" because it is a read-only property.
item.value = "Something";
});
// This works, but it's not ideal
produce(state, draft => {
const item = castDraft(findItemById(draft, 2));
item.value = "Something";
});我们正在遵循Immer.js关于将我们的国家设置为只读的建议。我们有一个“查询函数”,用于从状态中提取一个块。在本例中,这是findItemById()。我们很快就会遇到一个问题,每当我们从produce()中的草案实例中获取状态块时,它就失去了可变包装器,我们无法将其分配给它。我们可以使用函数castDraft()来解决这个问题,但我发现这并不理想--我们将在生产者中大量使用这类查询函数,因此需要始终进行转换是有问题的,而且容易出错(如果您不小心从外部状态而不是从草稿中转换,您就有麻烦了!)
我们要做的是定义函数findItemById()的类型,使其在输入State可写时返回可写State,否则返回不可变项。我尝试了以下几种方法:
function findItemById<T extends Draft<State> | State>(
state: T,
id: number
): T extends Draft<State> ? Draft<Item> : Item {
return state.items.find((x) => x.id === id)!;
}但这似乎行不通--结果总是可以写下来的。
发布于 2022-05-21 08:13:33
这是因为produce函数中的第二个State参数是一个包装在WritableDraft中的State。
让我们来看看什么是WritableDraft。
export declare type WritableDraft<T> = {
-readonly [K in keyof T]: Draft<T[K]>;
};正如您可能已经注意到的,这种类型通过第一级键并使它们成为mutable。换句话说,它是shallow mutable类型。Ut意味着所有丢失的属性仍然是不可变的。
此外,您的函数findItemById需要State参数,而在这里使用泛型更好,因为您用draft参数(即WritableDraft<State> )调用它。
因此,最好将其声明为:
function findItemById<S extends State>(state: S, id: number) {
return state.items.find((x) => x.id === id)!;
}但是,它仍然不起作用。让我们看一看castDraft (源代码):
export function castDraft<T>(value: T): Draft<T> {
return value as any
}和Draft类型:
export declare type Draft<T> = T extends PrimitiveType
? T
: T extends AtomicObject
? T
: T extends IfAvailable<ReadonlyMap<infer K, infer V>>
? Map<Draft<K>, Draft<V>>
: T extends IfAvailable<ReadonlySet<infer V>>
? Set<Draft<V>>
: T extends WeakReferences
? T
: T extends object
? WritableDraft<T>
: TcastDraft是一个shell函数,它只是在外壳下调用type assertion。Draft类型是一种递归类型,它贯穿于所有嵌套类型,这就是castDraft工作的原因。
也许值得在items上而不是state上操作
interface Item {
readonly id: number;
readonly value?: string;
}
interface State {
readonly items: Item[];
}
function findItemById<Items extends Item[]>(
items: Items,
id: number
): Draft<Item> {
return items.find((x) => x.id === id)!;
}
const state: State = {
items: [{ id: 1 }, { id: 2 }, { id: 3 }],
};
// This does not work.
produce(state.items, (draft) => {
const item = findItemById(draft, 2);
item.value = "Something"; // works
});https://stackoverflow.com/questions/72327676
复制相似问题