如何定义类型并确保它是打字稿中另一种类型的一部分?
How to define a type and make sure it's part of another type in typescript?
我有一个大类型:
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). That is, you can object types A
and B
where A extends B
and A
has properties that B
doesn't mention. 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 这使得它看起来对象类型是准确的)。
目前无法在 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 without needing special casing, and recursive mapped types that encounter primitive types leave them unmapped (see 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
的属性,如果 属性 键不存在于 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
),这些决定会影响接受哪些类型以及不接受哪些类型。但希望这至少能给你一条前进的道路。祝你好运!
我有一个大类型:
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). That is, you can object types A
and B
where A extends B
and A
has properties that B
doesn't mention. 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 这使得它看起来对象类型是准确的)。
目前无法在 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 without needing special casing, and recursive mapped types that encounter primitive types leave them unmapped (see 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
的属性,如果 属性 键不存在于 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
),这些决定会影响接受哪些类型以及不接受哪些类型。但希望这至少能给你一条前进的道路。祝你好运!