问题
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?
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[];
}>
Like so
回答1:
UPDATE 2018-06-22:
This answer was written a year ago, before the amazing conditional types feature was released in TypeScript 2.8. So this answer is no longer needed. Please see @krzysztof-kaczor's new answer below for the way to get this behavior in TypeScript 2.8 and up.
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:
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
.
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:
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
:
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:
interface User extends UserSchema<True> { } // required
interface DeepPartialUser extends UserSchema<False> { } // optional
And see it in action:
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
There you go. Hope that helps!
回答2:
With TS 2.8 and conditional types we can simply write:
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-essentials package for this and some other useful types.
回答3:
You can use ts-toolbelt, it can do operations on types at any depth
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
来源:https://stackoverflow.com/questions/45372227/how-to-implement-typescript-deep-partial-mapped-type-not-breaking-array-properti