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'>
Playground link
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?
Conditional types help describe the relation between the types of inputs and outputs. When the type on the left of the extends is assignable to the one on the right, then you'll get the type in the first branch (the “true” branch); otherwise you'll get the type in the latter branch (the “false” branch).
TypeScript Generics is a tool which provides a way to create reusable components. It creates a component that can work with a variety of data types rather than a single data type. It allows users to consume these components and use their own types.
Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.
TypeScript fully supports generics as a way to introduce type-safety into components that accept arguments and return values whose type will be indeterminate until they are consumed later in your code.
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 see T extend T
or T extends any
or T 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.
type StringConfig<T extends string> = { [K in T]: K };
type NumberConfig ={}
type ConfigMap<T> = [T] extends [number]
? NumberConfig
: [T] extends [string]
? StringConfig<T>
: never
export type MyConfig = ConfigMap<'a' | 'b' | 'c'> // so many sad faces
let x:MyConfig = {
a:'a',
b:'b',
c: 'c'
}
Playground Link
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With