I am having no luck understanding why the code below functions as it does:
type MapOverString<T extends string> = { [K in T]: K };
type IfStringMapOverIt<T> = T extends string ? MapOverString<T> : never;
type ThisWorks = MapOverString<'a'>;
// { a: 'a' }
type ThisAlsoWorks = IfStringMapOverIt<'a'>;
// { a: 'a' }
type Union = 'a' | 'b' | 'c';
type ThisWorksToo = MapOverString<Union>;
// { a: 'a', b: 'b', c: 'c' }
type ThisDoesnt = IfStringMapOverIt<Union>;
// MapOverString<'a'> | MapOverString<'b'> | MapOverString<'c'>
I must be missing something, because MapOverString
and IfStringMapOverIt
seem like they should function identically.
Ultimately, I am using string literals and generics to cascade through permutations of configuration types. For example, if you want StringConfig<T>
configured with options 'a' | 'b' | 'c'
:
type ConfigMap<T> = T extends number
? NumberConfig
: T extends string
? StringConfig<T>
: never
type MyConfig = ConfigMap<'a' | 'b' | 'c'> // so many sad faces
Could someone enlighten me? What's going on here?
This is an application of the distribution property of conditional types. A condition over naked type parameter, will trigger this behavior and
T extends string
satisfies this. You might also seeT extend T
orT extends any
orT extends unknown
used for this very reason, just to trigger distribution.You can read more about distributive conditional types in the handbook
You can disable distribution by using a condition over a tuple
[T] extends [string]
. The effect of this is similar to a regular condition, just since the type parameter is no longer naked distribution will be displayed.Playground Link