Typescript mappad typ, lägg till valfri modifierare villkorligt
Är det möjligt att göra en egenskap i en mappad typ valfri under vissa villkor?
Överväg denna typ
type Definition {
name: string,
defaultImplementation?: ImplementationType
}
och ett register över dem:
type DefinitionMap = Record<string, Definition>
Jag skulle vilja skapa en mappad typ som har en implementering som är valfri om indatat tillhandahålls, men den mappade typens implementering är obligatorisk om det inte gjordes.
För en DefinitionMap som denna
{
foo: { name: 'x' },
bar: { name: 'y', defaultImplementation: { /*...*/ } }
}
Jag skulle vilja ha en mappad typ som
{
foo: ImplementationType,
bar?: ImplementationType
}
Jag har försökt använda villkor och lägga till undefined till typen, men det fungerar inte.
type ImplementationMap<T extends DefinitionMap> = {
[K in keyof T]: T[K] extends { defaultImplementation: any }
? ImplementationType | undefined
: ImplementationType
}
Jag vet att de villkorliga grenarna beter sig som jag vill, men att lägga till undefined gör inte faktiskt fältet valfritt.
Svar #1
Jag antar att DefinitionMap bör vara Record<string, Definition> (istället för Record<string, A>).
Prova detta:
// Gets the keys of T whose values are assignable to V
type KeysMatching<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T]
type ImplementationMap<T extends DefinitionMap> =
// A partial (all properties are optional) record for all the keys
Partial<Record<keyof T, ImplementationType>> &
// Require ImplementationType for all the keys that do not have defaultImplementation
Record<KeysMatching<T, { defaultImplementation?: undefined }>, ImplementationType>
/*
Test is equivalent to
{
foo: ImplementationType,
bar?: ImplementationType,
baz: ImplementationType
}
*/
type Test = ImplementationMap<{
foo: { name: 'x' },
bar: { name: 'y', defaultImplementation: { /*...*/ } },
baz: { name: 'z', defaultImplementaiton: undefined }
}>
Svar #2
Här är en lösning:
type NonImplementedKeys<T extends DefinitionMap> = {[K in keyof T]: T[K] extends {defaultImplementation: ImplementationType} ? never : K}[keyof T]
type NiceIntersection<S, T> = {[K in keyof (S & T)]: (S & T)[K]}
type ImplementationMap<T extends DefinitionMap> = NiceIntersection<{
[K in NonImplementedKeys<T>]: ImplementationType
}, {
[K in keyof T]?: ImplementationType
}>
Exempel :
type DefinitionMapExample = {
foo: { name: 'x' },
bar: { name: 'y', defaultImplementation: { /*...*/ } }
}
// {foo: ImplementationType, bar?: ImplementationType | undefined}
type ImplementationMapExample = ImplementationMap<DefinitionMapExample>
Typen NiceIntersection<S, T> är ekvivalent med en vanlig intersection-typ S & T, förutom att den gör att resultatet ser ut som {foo: ..., bar?: ...} istället för {foo: ...} & {bar?: ...}.
Lekplatslänk
