我有一个大字型:
type BigType = {
aaa: string,
bbb?: number,
ccc: boolean[],
extra?: {
[key in string]?: string
},
nested1: {
nested2: {
nested3: {
[key in string]?: string
}
}
}
}
而且我想定义另一种类型并确保它是BigType
的子集,所以我定义了RecursivePartial
类型:
type RecursivePartial<T> = {
[P in keyof T]?:
T[P] extends (infer U)[] ? RecursivePartial<U>[] :
T[P] extends object ? RecursivePartial<T[P]> :
T[P];
};
和
type PartOf<T, X extends RecursivePartial<T>> = X;
现在我可以定义一个小的类型,它只是BigType的一部分:
type SmallType = PartOf<BigType, {
aaa: string;
extra: { ddd: string };
nested1: { nested2: {} }
}>
问题是我也可以添加不属于BigType
的属性:
type SmallType = PartOf<BigType, {
aaa: string;
extra: { ddd: string };
nested1: { nested2: {} },
someOtherProperty1: string, // not part of BigType
someOtherProperty2: string, // not part of BigType
}>
如何解决?
这里的问题是TypeScript中的对象类型是开放的,而不是exact (see #12936 for discussion about exact types)。也就是说,您可以对象类型A
和B
,其中A extends B
和A
具有B
没有提及的属性。这实际上是接口/类层次结构的关键部分。没有它,就无法将属性添加到子接口/子类。尽管如此,有时候它还是让人感到惊讶(特别是因为当您使用对象文字值时,编译器会执行附加的excess property checking,这使对象类型看起来像是精确的)。
确切的对象类型当前无法在TypeScript中表示为特定的具体类型。相反,您必须使用泛型(有关更多信息,请参见this GitHub comment)
无论如何,我可能会先定义DeepPartial
和DeepNoExcess
类型别名,然后在TypeOf
中使用它们。 DeepPartial
看起来像这样:
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
我认为这与您的
RecursivePartial
基本相同。从TS3.1开始,mapped types automatically map over arrays and tuples不需要特殊的大小写,并且遇到原始类型的递归映射类型将它们保留为未映射状态(请参见microsoft/TypeScript#12447)。这意味着您不需要做很多事情就可以获得递归Partial
。
[DeepNoExcess
必须同时采用主类型和候选类型(因为不可能具体表示精确类型):
type DeepNoExcess<T, U> = { [K in keyof U]: K extends keyof T ? DeepNoExcess<Required<T>[K], U[K]> : never };
这会遍历候选类型
U
的属性,如果never
中也不存在属性键,则使属性类型为T
。我不得不走进Required<T>
而不是T
,因为您的可选属性未得到正确处理(keyof (SomeType | undefined)
往往是never
)。
然后PartOf
的定义如下:
type PartOf<T, U extends DeepPartial<T> & DeepNoExcess<T, U>> = U;
这将产生您希望通过两个示例获得的行为:
type GoodSmallType = PartOf<BigType, { aaa: string; extra: { ddd: string }; nested1: { nested2: {} } }>; // okay type BadSmallType = PartOf<BigType, { aaa: string; extra: { ddd: string }; nested1: { nested2: {} }, someOtherProperty1: string, // not part of BigType someOtherProperty2: string, // not part of BigType }>; // error! Types of property 'someOtherProperty1' are incompatible.
它是否满足您的所有用例尚不清楚;您可以做出很多决定(例如
Required<T>
而不是T
),这些决定将影响接受什么类型和不接受什么类型。但是希望这至少可以给您前进的道路。祝你好运!