如何在不破坏数组属性的情况下实现 TypeScript 深度部分映射类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/45372227/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to implement TypeScript deep partial mapped type not breaking array properties
提问by Allan Simoyi
Any ideas as to how might apply TypeScript's Partial mapped type to an interface recursively, at the same time not breaking any keys with array return types?
关于如何将 TypeScript 的 Partial 映射类型递归地应用于接口的任何想法,同时不破坏具有数组返回类型的任何键?
The following approaches have not been sufficing:
以下方法还不够:
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 ]>;
}
Any ideas?
有任何想法吗?
UPDATE: Accepted answer - A better and more general solve for now.
更新:接受的答案 - 现在更好,更通用的解决方案。
Had found a temporary workaround which involves intersection of types and two mapped types as follows. The most notable drawback is that you have to supply the property overrides to restore sullied keys, the ones with array return types.
找到了一个临时解决方法,它涉及类型和两个映射类型的交集,如下所示。最显着的缺点是您必须提供属性覆盖来恢复被污染的键,即具有数组返回类型的键。
E.g.
例如
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[];
}>
采纳答案by jcalz
UPDATE 2018-06-22:
更新 2018-06-22:
This answer was written a year ago, before the amazing conditional typesfeature was released in TypeScript 2.8. So this answer is no longer needed. Please see @krzysztof-kaczor's new answerbelow for the way to get this behavior in TypeScript 2.8 and up.
这个答案是在一年前写的,在 TypeScript 2.8 中发布惊人的条件类型功能之前。所以不再需要这个答案。请参阅下面的@krzysztof-kaczor 的新答案,了解在 TypeScript 2.8 及更高版本中获得此行为的方法。
Okay, here is my best attempt at a crazy but fully general solution (requiring TypeScript 2.4 and up) which might not worth it to you, but if you want to use it, be my guest:
好的,这是我对疯狂但完全通用的解决方案(需要 TypeScript 2.4 及更高版本)的最佳尝试,这对您来说可能不值得,但如果您想使用它,请成为我的客人:
First, we need some type-level boolean logic:
首先,我们需要一些类型级别的布尔逻辑:
type False = '0'
type True = '1'
type Bool = False | True
type IfElse<Cond extends Bool, Then, Else> = {'0': Else; '1': Then;}[Cond];
All you need to know here is that the type IfElse<True,A,B>
evaluates to A
and IfElse<False,A,B>
evaluates to B
.
在这里您需要知道的只是该类型的IfElse<True,A,B>
计算结果为A
并且IfElse<False,A,B>
计算结果为B
。
Now we define a record type Rec<K,V,X>
, an object with key K
and value type V
, where Rec<K,V,True>
means the property is required, and Rec<K,V,False>
means the property is optional:
现在我们定义一个记录类型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>>>
At this point we can get to your User
and DeepPartialUser
types. Let's describe a general UserSchema<R>
where every property we care about is either required or optional, depending on whether R
is True
or False
:
在这一点上,我们可以得到你的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>
Ugly, right? But we can finally describe both User
and DeepPartialUser
as:
丑对不对?但是,我们终于可以同时描述User
和DeepPartialUser
如下:
interface User extends UserSchema<True> { } // required
interface DeepPartialUser extends UserSchema<False> { } // optional
And see it in action:
并在行动中看到它:
var user: User = {
emailAddress: '[email protected]',
verification: {
verified: true,
verificationCode: 'shazam'
},
activeApps: ['netflix','facebook','angrybirds']
} // any missing properties or extra will cause an error
var deepPartialUser: DeepPartialUser = {
emailAddress: '[email protected]',
verification: {
verified: false
}
} // missing properties are fine, extra will still error
There you go. Hope that helps!
你去吧。希望有帮助!
回答by Krzysztof Kaczor
With TS 2.8 and conditional types we can simply write:
使用 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]>
};
You might want to checkout https://github.com/krzkaczor/ts-essentialspackage for this and some other useful types.
您可能想查看https://github.com/krzkaczor/ts-essentials包以了解此包和其他一些有用的类型。
回答by Nickofthyme
I started with @krzysztof's answer but have since been iterating on it when I come across edge cases. Specifically the edge cases below, based on the given value of the base object (i.e. T[P]
):
我从@krzysztof的答案开始,但是当我遇到边缘情况时一直在迭代它。特别是下面的边缘情况,基于基础对象的给定值(即T[P]
):
any
any[]
ReadonlyArray<any>
Map
Set
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
的
NonAny
类型被用于检查any
值
回答by pirix-gh
You can use ts-toolbelt, it can do operations on types at any depth
您可以使用ts-toolbelt,它可以对任何深度的类型进行操作
In your case, it would be:
在您的情况下,它将是:
import {O} from 'ts-toolbelt'
interface User {
emailAddress: string;
verification: {
verified: boolean;
verificationCode: string;
}
activeApps: string[];
}
type optional = O.Optional<User, keyof User, 'deep'>
And if you want to compute it deeply (for display purposes), you can use Compute
for that
如果要计算它深深地(用于显示目的),您可以使用Compute
该