Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript Distributive Conditional Types

Tags:

typescript

So i was going through the documentation on typescript and am not able to get my head around this concept.

So the documentation states :-

In instantiations of a distributive conditional type T extends U ? X : Y, references to T within the conditional type are resolved to individual constituents of the union type (i.e. T refers to the individual constituents after the conditional type is distributed over the union type). Furthermore, references to T within X have an additional type parameter constraint U (i.e. T is considered assignable to U within X).

I cannot understand the part T refers to the individual constituents after the conditional type is distributed over the union type.

Can anyone please explain this to me. An example of the same would be highly appreciated, the one in the documentation is not very clear to me.

like image 824
Amol Gupta Avatar asked Mar 27 '19 16:03

Amol Gupta


People also ask

What are @types TypeScript?

What is a type in TypeScript. In TypeScript, a type is a convenient way to refer to the different properties and functions that a value has. A value is anything that you can assign to a variable e.g., a number, a string, an array, an object, and a function. When you look at this value, you can say that it's a string.

What does ?: Mean in TypeScript?

What does ?: mean in TypeScript? Using a question mark followed by a colon ( ?: ) means a property is optional. That said, a property can either have a value based on the type defined or its value can be undefined .

How do you add a condition in TypeScript?

Syntax: We can create the conditional types with the used of ternary operator and extends in TypeScript. Type1 extends Type2 ? for One value : for different value; Here extend works as compare function which checks is Type1 have properties of Type2 if yes then it jumps to true branch else in the false branch.

How do you write a conditional statement in TypeScript?

Following is an example TypeScript code to demonstrate if conditional statement. var a:number = 1 var b:number = 3 if(a == 1){ console. log("value of a is 1.") } else { console. log("value of a is not 1.") } if(a == b){ console.


2 Answers

Hmm, I just read through the documentation and it makes sense to me... I don't know if I can explain it any better than that, but let's go through it. In what follows, and ...x..., means "some expression in which x might appear".

Conditional types in which the checked type is a naked type parameter are called distributive conditional types.

In this case, a type parameter means a generic type parameter, and a naked type parameter is a type expression where the type parameter appears alone and is not part of some more complex type expression. And the checked type is the type appearing before extends. Let's see some examples:

  • type A<T> = string extends T ? "yes" : "no" This is not a distributive conditional type. The checked type is string, which is not a generic type parameter.
  • type B<T> = {x: T} extends {x: number} ? "yes" : "no" This is not a distributive conditional type. The checked type is {x: T}, which has the type parameter T in it, but is not a naked type parameter.
  • type C<T> = T extends string ? "yes" : "no" This is a distributive conditional type; the checked type is T, which is a naked generic type parameter.

Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y with the type argument A | B | C for T is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

This is the essence of what a distributive property does. If you have a type alias F<T> defined to be a distributive conditional type, as in:

type F<T> = T extends ...T... ? ...T... : ...T...

Then F<T> will distribute over unions, meaning that for any types A and B, the type F<A | B> will be equivalent to the type F<A> | F<B>

In instantiations of a distributive conditional type T extends U ? X : Y, references to T within the conditional type are resolved to individual constituents of the union type (i.e. T refers to the individual constituents after the conditional type is distributed over the union type).

This is the part that confused you, but it's just explaining how the distribution works. It's saying that to evaluate F<A | B>, you should evaluate F<A> | F<B>. So for F<A>, you take F<T> = T extends ...T... ? ...T... : ...T... and plug in A for T (to get A extends ...A... ? ...A... : ...A...), and then plug in B for T (to get B extends ...B... ? ...B... : ...B...), and then unite them.

Let's go through a concrete example:

type D<T> = T extends string ? T : "nope"

What is this:

type E = D<"a" | "b" | 0 | true> 

Well, here's how not to do it:

type E = ("a" | "b" | 0 | true) extends string ? ("a" | "b" | 0 | true) : "nope" //👎

type E = "nope" //👎

I just plugged "a" | "b" | 0 | true into T without distributing, and that's wrong. Here's how to do it correctly:

type E = D<"a"> | D<"b"> | D<0> | D<true> //👍

type E = ("a" extends string ? "a" : "nope") |
         ("b" extends string ? "b" : "nope") |
         (0 extends string ? 0 : "nope") |
         (true extends string ? true : "nope") //👍

type E = ("a") | ("b") | ("nope") | ("nope") //👍

type E = "a" | "b" | "nope" //👍

See, we took the "individual constituents of the union" and replaced T with each one of them in turn.

Okay, I hope that makes more sense now. Good luck!

like image 95
jcalz Avatar answered Oct 05 '22 09:10

jcalz


Within a distributive conditional type (let's say type BoxIfObject<T> = T extends object ? Array<T> : T;) when the type is applied to a union (let's say number | { a : string }), it's as if the conditional type is applied to each constituent of the union and thus within the conditional type T will in turn refer to each constituent of the union (so T will first be number and then T will be { a : string })

So when we apply BoxIfObject<number | { a : string }>, T will never refer to the whole union number | { a : string } but to each of it's constituents in turn. Basically BoxIfObject<number | { a : string }> = BoxIfObject<number> | BoxIfObject<{ a : string }> = number | Array<{ a : string }

like image 24
Titian Cernicova-Dragomir Avatar answered Oct 05 '22 08:10

Titian Cernicova-Dragomir