interface ContextValue {
[K: string]: {
value: any
error: boolean
errorMessage: string
}
}
export interface GatheringExpChildMachineBaseContext<TYPE> extends Omit<ContextValue, 'id' | 'originalData' | 'order'> {
id?: string
originalData: TYPE
order: number
}
我收到错误消息TS 2411 Property 'id' of type 'string' is not assignable to string index type '{ value: any; error: boolean; errorMessage: string; }'.
以及与originalData
和order
等效的消息。
这似乎是不正确的,因为忽略应该抓住ContextValue
界面的键(可以是任何键),并忽略我提供的'id' | 'originalData' | 'order'
的并集类型,并允许我覆盖它们。
答案 0 :(得分:2)
TypeScript当前不支持减法/ negated types,这就是您需要说的“ string
但not ("id" | "originalData" | "order")
”的意思。通过使用键上的Exclude<T, U>
utility type来实现Omit
。 Exclude<T, U>
过滤T
的子类型U
的任何并集成员。由于string
不是联合,因此筛选器将整体上作用于string
。而且,由于string
不是字符串文字的有限并集的子类型(它是超类型,而不是子类型),因此Exclude<string, "id" | "originalData" | "order">
的值仅为string
。从某种意义上讲,当前的TypeScript版本认为“ {string
减去("id" | "originalData" | "order")
等于string
”。
您正在寻找的特定功能(可以在其中对索引签名进行例外处理)是GitHub中一个未解决问题的主题:microsoft/TypeScript#17867。如果您想在TypeScript中实现此功能,则可能要解决该问题并给它一个a。它也标记为“等待更多反馈”,因此,如果您认为它有一个引人注目的用例(尚未涵盖),则可能需要发表评论以详细说明它。
目前只有多种解决方法,每种解决方法都有一个相关的痛点。
您可以使用交集来避免该错误,从而可以轻松使用该类型的现有值:
interface Extension<T> { id?: string, originalData: T, order: number }
type GECMBCIntersection<T> = ContextValue & Extension<T>
可以轻松使用该类型的现有值
declare const i: GECMBCIntersection<number>;
i.id?.toUpperCase(); // okay
i.originalData.toFixed(); // okay
i.order.toFixed(); // okay
i.somethingElse.errorMessage.toUpperCase(); // okay
但是,出于相同的不兼容索引签名原因,要验证值是否属于该类型并不容易:
const iBad: GECMBCIntersection<number> = {
originalData: 1,
order: 2
} // error! originalData is not assignable to index signature
您可以在实际键中将其表示为generic constraint,而不是带有索引签名的具体类型:
type GECMBCConstraint<K extends PropertyKey, T> =
Record<Exclude<K, keyof Extension<any>>, ContextValue[string]> & Extension<T>;
但是随后您需要在每个地方都携带一个额外的泛型类型参数,并使用辅助函数创建没有冗余键名的实例:
const asGECMBCConstraint = <G extends GECMBCConstraint<keyof G, any>>(g: G) => g;
const cGood = asGECMBCConstraint({
originalData: 1,
order: 2,
somethingElse: {
value: "blork",
error: true,
errorMessage: "blork!!",
}
})
即使这样,当您要访问erstwhile-index-signature属性时,也很难使用实际值:
function cBad<G extends GECMBCConstraint<keyof G, any>>(g: G) {
if ("somethingElse" in g) {
g.somethingElse; // error
}
const asserted = g as GECMBCIntersection<any>;
if ("somethingElse" in asserted) {
asserted.somethingElse.errorMessage.toUpperCase() // okay
}
}
现在,我通常建议重构代码,以免将索引签名与其他属性混在一起。如果可以,请重写您的类型,使其具有为ContextValue
,而不是 为ContextValue
。 (这也称为composition over inheritance)。然后,您将获得更简单的界面:
interface Easier<T> extends Extension<T> {
contextValues: ContextValue;
}
这对于编译器来说很容易推理:
const easy: Easier<number> = {
originalData: 1,
order: 2,
contextValues: {
somethingElse: {
value: "blork",
error: true,
errorMessage: "blork!!",
}
}
};
好的,希望能有所帮助;祝你好运!