Advanced TypeScript Exercises - Answer 1
Pragmatic Maciej
Posted on February 7, 2020
The Question I've asked was:
If we have a type which is wrapped type like
Promise
. How we can get a type which is inside the wrapped type? For example if we havePromise<ExampleType>
how to getExampleType
?
The answer
type Transform<A> = A extends Promise<infer Inner> ? Inner : never
type Result = Transform<Promise<string>> // Result is string type
In order to unwrap the promise type we have used infer
keyword.
The keyword is helpful with any type constructor, type constructor is a type parameterized by another type variable, so any type which has generic placeholder like A<B>
, where A
is type constructor parametrized by B
.
Examples of using infer
We can use infer
also with other types, consider example with Array
type InsideArray<A> = A extends Array<infer Inside> ? Inside : never
type Str = InsideArray<Array<string>>; // Str is string
What about custom parameterized types? Yes will do!
type Surprise<A> = { inside: A }
type UnpackSurprise<S> = S extends Surprise<infer Inside> ? Inside : never
type Num = UnpackSurprise<Surprise<number>> // Num is number
We can even use infer
to get mapped types properties
type User = {
id: number,
name: string,
}
type Doc = {
id: string,
}
type GetProperty<T, Prop extends keyof T> = T extends { [K in Prop]: infer Value } ? Value : never
type UserId = GetProperty<User, 'id'>
type DocId = GetProperty<Doc, 'id'>
Question How we can get type of Mapped type property in simpler way? Please write your answer in the comment!
Can we use many type variables and infer them? Sure we can!
type ABC<A, B, C> = { a: A, b: B, c: C }
type ABCIntoTuple<T>
= T extends ABC<infer A, infer B, infer C> ? [A, B, C] : never
type Example = ABC<string, boolean, number>
type ExampleTuple = ABCIntoTuple<Example> // [string, boolean, number]
In above example we infer all three type parameters and we put them into 3-n tuple.
Why never
?
Type never
is a bottom type, it is a type without any value, it is very handy construct for saying that our function doesn't return, or some path of the code is not reachable, more about it you can read in great article from Marius Schulz.
We use never
in conditional in order to represent the unhappy path, we are saying that it is a dead end, if you don't pass to our constructor specific type, we just don't have any alternative, our type doesn't work with anything else. Consider how it will behave when we pass to it something which does not match the condition:
type Transform<A> = A extends Promise<infer Inner> ? Inner : never
type OhGosh = Transform<string> // OhGosh evaluates to never
We could be having different representation of the negative path, but never is the best choice, as further type transformation will be useless. We can also set constraint at the argument, and in that way never
path will never be reached.
Consider following change:
type Transform<A extends Promise<any>> = A extends Promise<infer Inner> ? Inner : never
type OhGosh = Transform<string> // compilation error
After A extends Promise<any>
our utility type Transform
is now bullet proof, as compilation will fail for types which do not extend Promise<any>
.
Why did I use
Promise<any>
?
I have put any
inside Promise
because any
is one of unsound types which are also types which can be assigned to everything, it means that every type extend from any
, what determines that every Promise
kind of type will extend Promise<any>
This series is just starting. If you want to know about new exciting questions from advanced TypeScript please follow me on dev.to and twitter.
Posted on February 7, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024