[英]How to define a type and make sure it's part of another type in typescript?
I have a big type:我有一个大类型:
type BigType = {
aaa: string,
bbb?: number,
ccc: boolean[],
extra?: {
[key in string]?: string
},
nested1: {
nested2: {
nested3: {
[key in string]?: string
}
}
}
}
And I want to define another type and make sure it's subset of the BigType
, so I defined a RecursivePartial
type:我想定义另一种类型并确保它是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];
};
and和
type PartOf<T, X extends RecursivePartial<T>> = X;
Now I can define a small type which is only part of the BigType:现在我可以定义一个小类型,它只是 BigType 的一部分:
type SmallType = PartOf<BigType, {
aaa: string;
extra: { ddd: string };
nested1: { nested2: {} }
}>
The problem is I can add properties which is not part of BigType
too:问题是我也可以添加不属于BigType
的属性:
type SmallType = PartOf<BigType, {
aaa: string;
extra: { ddd: string };
nested1: { nested2: {} },
someOtherProperty1: string, // not part of BigType
someOtherProperty2: string, // not part of BigType
}>
How to fix it?如何解决?
The issue here is that object types in TypeScript are open and not exact (see #12936 for discussion about exact types) .这里的问题是 TypeScript 中的 object 类型是开放的并且不准确(有关确切类型的讨论,请参见#12936) 。 That is, you can object types A
and B
where A extends B
and A
has properties that B
doesn't mention.也就是说,您可以 object 类型A
和B
其中A extends B
并且A
具有B
未提及的属性。 This is actually a crucial part of interface/class hierarchies;这实际上是接口/类层次结构的关键部分; without it, you couldn't add properties to subinterfaces/subclasses.没有它,您将无法向子接口/子类添加属性。 Still, there are times when it surprises people (especially because when you use object literal values the compiler performs additional excess property checking which makes it look like object types are exact).尽管如此,有时它还是会让人们感到惊讶(特别是因为当您使用 object 文字值时,编译器会执行额外的额外属性检查,这使得 object 类型看起来是准确的)。
Exact object types can't currently be represented in TypeScript as specific concrete types.确切的 object 类型目前无法在 TypeScript 中表示为特定的具体类型。 Instead, you have to use generics (see this GitHub comment for more information)相反,您必须使用 generics (有关更多信息,请参阅此 GitHub 评论)
Anyway in your case I'd probably proceed by defining a DeepPartial
and a DeepNoExcess
type alias and using both of them in TypeOf
.无论如何,在您的情况下,我可能会继续定义DeepPartial
和DeepNoExcess
类型别名并在TypeOf
中使用它们。 DeepPartial
looks like this: DeepPartial
看起来像这样:
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
This is essentially the same as your RecursivePartial
, I think.我认为这与您的RecursivePartial
基本相同。 As of TS3.1, mapped types automatically map over arrays and tuples without needing special casing, and recursive mapped types that encounter primitive types leave them unmapped (see microsoft/TypeScript#12447 ).从 TS3.1 开始, 映射类型在 arrays 和元组上自动 map 和不需要特殊大小写的元组,遇到原始类型的递归映射类型使它们未映射(参见microsoft/TypeScript#12447 )。 That means you don't need to do much to get a recursive Partial
.这意味着您无需做太多事情即可获得递归Partial
。
DeepNoExcess
has to take both the main type and the candidate type (since it's not possible to represent exact types concretely): DeepNoExcess
必须同时采用主要类型和候选类型(因为无法具体表示确切的类型):
type DeepNoExcess<T, U> = { [K in keyof U]:
K extends keyof T ? DeepNoExcess<Required<T>[K], U[K]> :
never };
This walks down through the properties of the candidate type U
and makes the property type never
if the property key doesn't also exist in T
.这将遍历候选类型U
的属性,并且如果属性键也不存在于T
中,则使属性类型never
出现。 I had to walk down into Required<T>
instead of just T
because your optional properties weren't being handled properly ( keyof (SomeType | undefined)
tends to be never
).我不得不走进Required<T>
而不仅仅是T
因为您的可选属性没有得到正确处理( keyof (SomeType | undefined)
往往是never
)。
Then PartOf
is defined like this:然后PartOf
定义如下:
type PartOf<T, U extends DeepPartial<T> & DeepNoExcess<T, U>> = U;
This yields the behavior you are hoping for with your two examples:这会产生您希望通过两个示例实现的行为:
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.
Whether it meets all your use cases is not clear;它是否满足您的所有用例尚不清楚; there are lots of decisions you could make (like Required<T>
instead of T
) that will have implications for what types are accepted and what types are not.您可以做出很多决定(例如Required<T>
而不是T
),这将对接受哪些类型以及不接受哪些类型产生影响。 But hopefully this gives you a way forward at least.但希望这至少能给你一条前进的道路。 Good luck!祝你好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.