如何在不破坏数组属性的情况下实现 TypeScript 深度部分映射类型
How to implement TypeScript deep partial mapped type not breaking array properties
关于如何递归地将 TypeScript 的部分映射类型应用于接口,同时不破坏数组 return 类型的任何键的任何想法?
以下方法还不够:
interface User {
emailAddress: string;
verification: {
verified: boolean;
verificationCode: string;
}
activeApps: string[];
}
type PartialUser = Partial<User>; // does not affect properties of verification
type PartialUser2 = DeepPartial<User>; // breaks activeApps' array return type;
export type DeepPartial<T> = {
[ P in keyof T ]?: DeepPartial<T[ P ]>;
}
有什么想法吗?
更新:
已接受的答案 - 目前更好、更通用的解决方案。
已找到一个临时解决方法,它涉及类型的交集和两个映射类型,如下所示。最显着的缺点是您必须提供 属性 覆盖来恢复损坏的键,即具有数组 return 类型的键。
例如
type PartialDeep<T> = {
[ P in keyof T ]?: PartialDeep<T[ P ]>;
}
type PartialRestoreArrays<K> = {
[ P in keyof K ]?: K[ P ];
}
export type DeepPartial<T, K> = PartialDeep<T> & PartialRestoreArrays<K>;
interface User {
emailAddress: string;
verification: {
verified: boolean;
verificationCode: string;
}
activeApps: string[];
}
export type AddDetailsPartialed = DeepPartial<User, {
activeApps?: string[];
}>
Like so
2018-06-22 更新:
这个答案是一年前写的,在下面关于在 TypeScript 2.8 及更高版本中获得此行为的惊人 conditional types feature was released in TypeScript 2.8. So this answer is no longer needed. Please see @krzysztof-kaczor's 之前。
好的,这是我对一个疯狂但完全通用的解决方案(需要 TypeScript 2.4 及更高版本)的最佳尝试,这对你来说可能不值得,但如果你想使用它,请成为我的客人:
首先,我们需要一些类型级别的布尔逻辑:
type False = '0'
type True = '1'
type Bool = False | True
type IfElse<Cond extends Bool, Then, Else> = {'0': Else; '1': Then;}[Cond];
您在这里只需要知道类型 IfElse<True,A,B>
的计算结果为 A
并且 IfElse<False,A,B>
的计算结果为 B
.
现在我们定义一个记录类型Rec<K,V,X>
,一个键为K
,值类型为V
的对象,其中Rec<K,V,True>
表示属性是必需,Rec<K,V,False>
表示属性是可选:
type Rec<K extends string, V, Required extends Bool> = IfElse<Required, Record<K, V>, Partial<Record<K, V>>>
此时我们可以得到您的 User
和 DeepPartialUser
类型。让我们描述一个通用的 UserSchema<R>
,其中我们关心的每个 属性 都是必需的或可选的,具体取决于 R
是 True
还是 False
:
type UserSchema<R extends Bool> =
Rec<'emailAddress', string, R> &
Rec<'verification', (
Rec<'verified', boolean, R> &
Rec<'verificationCode', string, R>
), R> &
Rec<'activeApps', string[], R>
丑吧?但我们最终可以将 User
和 DeepPartialUser
描述为:
interface User extends UserSchema<True> { } // required
interface DeepPartialUser extends UserSchema<False> { } // optional
看看实际效果:
var user: User = {
emailAddress: 'foo@example.com',
verification: {
verified: true,
verificationCode: 'shazam'
},
activeApps: ['netflix','facebook','angrybirds']
} // any missing properties or extra will cause an error
var deepPartialUser: DeepPartialUser = {
emailAddress: 'bar@example.com',
verification: {
verified: false
}
} // missing properties are fine, extra will still error
好了。希望对您有所帮助!
使用 TS 2.8 和条件类型我们可以简单地写:
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: DeepPartial<T[P]>
};
或者用 []
而不是 Array<>
会是:
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? DeepPartial<U>[]
: T[P] extends Readonly<infer U>[]
? Readonly<DeepPartial<U>>[]
: DeepPartial<T[P]>
};
您可能需要检查 https://github.com/krzkaczor/ts-essentials 包以获取此类型和其他一些有用的类型。
你可以使用ts-toolbelt,它可以对任何深度的类型进行操作
在你的情况下,它将是:
import {O} from 'ts-toolbelt'
interface User {
emailAddress: string;
verification: {
verified: boolean;
verificationCode: string;
}
activeApps: string[];
}
type optional = O.Optional<User, keyof User, 'deep'>
如果你想深入计算它(为了显示目的),你可以使用 Compute
我从 @krzysztof 的回答开始,但是当我遇到边缘情况时一直在迭代它。特别是下面的边缘情况,基于基础对象的给定值(即 T[P]
):
any
any[]
ReadonlyArray<any>
Map
Set
type NonAny = number | boolean | string | symbol | null;
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends NonAny[] // checks for nested any[]
? T[P]
: T[P] extends ReadonlyArray<NonAny> // checks for nested ReadonlyArray<any>
? T[P]
: T[P] extends (infer U)[]
? DeepPartial<U>[]
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T[P] extends Set<infer V> // checks for Sets
? Set<DeepPartial<V>>
: T[P] extends Map<infer K, infer V> // checks for Maps
? Map<K, DeepPartial<V>>
: T[P] extends NonAny // checks for primative values
? T[P]
: DeepPartial<T[P]>; // recurse for all non-array and non-primative values
};
The NonAny
type is used to check for any
values
这似乎工作正常。
type DeepOptional<T> =
T extends Date | Function ? T
: T extends (infer R)[] ? DeepOptional<R>[]
: T extends Record<PropertyKey, any> ?
{
[K in keyof T]?: DeepOptional<T[K]>
}
: T;
关于如何递归地将 TypeScript 的部分映射类型应用于接口,同时不破坏数组 return 类型的任何键的任何想法?
以下方法还不够:
interface User {
emailAddress: string;
verification: {
verified: boolean;
verificationCode: string;
}
activeApps: string[];
}
type PartialUser = Partial<User>; // does not affect properties of verification
type PartialUser2 = DeepPartial<User>; // breaks activeApps' array return type;
export type DeepPartial<T> = {
[ P in keyof T ]?: DeepPartial<T[ P ]>;
}
有什么想法吗?
更新: 已接受的答案 - 目前更好、更通用的解决方案。
已找到一个临时解决方法,它涉及类型的交集和两个映射类型,如下所示。最显着的缺点是您必须提供 属性 覆盖来恢复损坏的键,即具有数组 return 类型的键。
例如
type PartialDeep<T> = {
[ P in keyof T ]?: PartialDeep<T[ P ]>;
}
type PartialRestoreArrays<K> = {
[ P in keyof K ]?: K[ P ];
}
export type DeepPartial<T, K> = PartialDeep<T> & PartialRestoreArrays<K>;
interface User {
emailAddress: string;
verification: {
verified: boolean;
verificationCode: string;
}
activeApps: string[];
}
export type AddDetailsPartialed = DeepPartial<User, {
activeApps?: string[];
}>
Like so
2018-06-22 更新:
这个答案是一年前写的,在下面关于在 TypeScript 2.8 及更高版本中获得此行为的惊人 conditional types feature was released in TypeScript 2.8. So this answer is no longer needed. Please see @krzysztof-kaczor's
好的,这是我对一个疯狂但完全通用的解决方案(需要 TypeScript 2.4 及更高版本)的最佳尝试,这对你来说可能不值得,但如果你想使用它,请成为我的客人:
首先,我们需要一些类型级别的布尔逻辑:
type False = '0'
type True = '1'
type Bool = False | True
type IfElse<Cond extends Bool, Then, Else> = {'0': Else; '1': Then;}[Cond];
您在这里只需要知道类型 IfElse<True,A,B>
的计算结果为 A
并且 IfElse<False,A,B>
的计算结果为 B
.
现在我们定义一个记录类型Rec<K,V,X>
,一个键为K
,值类型为V
的对象,其中Rec<K,V,True>
表示属性是必需,Rec<K,V,False>
表示属性是可选:
type Rec<K extends string, V, Required extends Bool> = IfElse<Required, Record<K, V>, Partial<Record<K, V>>>
此时我们可以得到您的 User
和 DeepPartialUser
类型。让我们描述一个通用的 UserSchema<R>
,其中我们关心的每个 属性 都是必需的或可选的,具体取决于 R
是 True
还是 False
:
type UserSchema<R extends Bool> =
Rec<'emailAddress', string, R> &
Rec<'verification', (
Rec<'verified', boolean, R> &
Rec<'verificationCode', string, R>
), R> &
Rec<'activeApps', string[], R>
丑吧?但我们最终可以将 User
和 DeepPartialUser
描述为:
interface User extends UserSchema<True> { } // required
interface DeepPartialUser extends UserSchema<False> { } // optional
看看实际效果:
var user: User = {
emailAddress: 'foo@example.com',
verification: {
verified: true,
verificationCode: 'shazam'
},
activeApps: ['netflix','facebook','angrybirds']
} // any missing properties or extra will cause an error
var deepPartialUser: DeepPartialUser = {
emailAddress: 'bar@example.com',
verification: {
verified: false
}
} // missing properties are fine, extra will still error
好了。希望对您有所帮助!
使用 TS 2.8 和条件类型我们可以简单地写:
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: DeepPartial<T[P]>
};
或者用 []
而不是 Array<>
会是:
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? DeepPartial<U>[]
: T[P] extends Readonly<infer U>[]
? Readonly<DeepPartial<U>>[]
: DeepPartial<T[P]>
};
您可能需要检查 https://github.com/krzkaczor/ts-essentials 包以获取此类型和其他一些有用的类型。
你可以使用ts-toolbelt,它可以对任何深度的类型进行操作
在你的情况下,它将是:
import {O} from 'ts-toolbelt'
interface User {
emailAddress: string;
verification: {
verified: boolean;
verificationCode: string;
}
activeApps: string[];
}
type optional = O.Optional<User, keyof User, 'deep'>
如果你想深入计算它(为了显示目的),你可以使用 Compute
我从 @krzysztof 的回答开始,但是当我遇到边缘情况时一直在迭代它。特别是下面的边缘情况,基于基础对象的给定值(即 T[P]
):
any
any[]
ReadonlyArray<any>
Map
Set
type NonAny = number | boolean | string | symbol | null;
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends NonAny[] // checks for nested any[]
? T[P]
: T[P] extends ReadonlyArray<NonAny> // checks for nested ReadonlyArray<any>
? T[P]
: T[P] extends (infer U)[]
? DeepPartial<U>[]
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T[P] extends Set<infer V> // checks for Sets
? Set<DeepPartial<V>>
: T[P] extends Map<infer K, infer V> // checks for Maps
? Map<K, DeepPartial<V>>
: T[P] extends NonAny // checks for primative values
? T[P]
: DeepPartial<T[P]>; // recurse for all non-array and non-primative values
};
The
NonAny
type is used to check forany
values
这似乎工作正常。
type DeepOptional<T> =
T extends Date | Function ? T
: T extends (infer R)[] ? DeepOptional<R>[]
: T extends Record<PropertyKey, any> ?
{
[K in keyof T]?: DeepOptional<T[K]>
}
: T;