Skip to content

Commit 4db337c

Browse files
committed
add original doc
1 parent 9819f47 commit 4db337c

File tree

1 file changed

+271
-0
lines changed

1 file changed

+271
-0
lines changed
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
---
2+
title: Conditional Types
3+
layout: docs
4+
permalink: /docs/handbook/2/conditional-types.html
5+
oneline: "Create types which act like if statements in the type system."
6+
---
7+
8+
At the heart of most useful programs, we have to make decisions based on input.
9+
JavaScript programs are no different, but given the fact that values can be easily introspected, those decisions are also based on the types of the inputs.
10+
_Conditional types_ help describe the relation between the types of inputs and outputs.
11+
12+
```ts twoslash
13+
interface Animal {
14+
live(): void;
15+
}
16+
interface Dog extends Animal {
17+
woof(): void;
18+
}
19+
20+
type Example1 = Dog extends Animal ? number : string;
21+
// ^?
22+
23+
type Example2 = RegExp extends Animal ? number : string;
24+
// ^?
25+
```
26+
27+
Conditional types take a form that looks a little like conditional expressions (`condition ? trueExpression : falseExpression`) in JavaScript:
28+
29+
```ts twoslash
30+
type SomeType = any;
31+
type OtherType = any;
32+
type TrueType = any;
33+
type FalseType = any;
34+
type Stuff =
35+
// ---cut---
36+
SomeType extends OtherType ? TrueType : FalseType;
37+
```
38+
39+
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).
40+
41+
From the examples above, conditional types might not immediately seem useful - we can tell ourselves whether or not `Dog extends Animal` and pick `number` or `string`!
42+
But the power of conditional types comes from using them with generics.
43+
44+
For example, let's take the following `createLabel` function:
45+
46+
```ts twoslash
47+
interface IdLabel {
48+
id: number /* some fields */;
49+
}
50+
interface NameLabel {
51+
name: string /* other fields */;
52+
}
53+
54+
function createLabel(id: number): IdLabel;
55+
function createLabel(name: string): NameLabel;
56+
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
57+
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
58+
throw "unimplemented";
59+
}
60+
```
61+
62+
These overloads for createLabel describe a single JavaScript function that makes a choice based on the types of its inputs. Note a few things:
63+
64+
1. If a library has to make the same sort of choice over and over throughout its API, this becomes cumbersome.
65+
2. We have to create three overloads: one for each case when we're _sure_ of the type (one for `string` and one for `number`), and one for the most general case (taking a `string | number`). For every new type `createLabel` can handle, the number of overloads grows exponentially.
66+
67+
Instead, we can encode that logic in a conditional type:
68+
69+
```ts twoslash
70+
interface IdLabel {
71+
id: number /* some fields */;
72+
}
73+
interface NameLabel {
74+
name: string /* other fields */;
75+
}
76+
// ---cut---
77+
type NameOrId<T extends number | string> = T extends number
78+
? IdLabel
79+
: NameLabel;
80+
```
81+
82+
We can then use that conditional type to simplify our overloads down to a single function with no overloads.
83+
84+
```ts twoslash
85+
interface IdLabel {
86+
id: number /* some fields */;
87+
}
88+
interface NameLabel {
89+
name: string /* other fields */;
90+
}
91+
type NameOrId<T extends number | string> = T extends number
92+
? IdLabel
93+
: NameLabel;
94+
// ---cut---
95+
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
96+
throw "unimplemented";
97+
}
98+
99+
let a = createLabel("typescript");
100+
// ^?
101+
102+
let b = createLabel(2.8);
103+
// ^?
104+
105+
let c = createLabel(Math.random() ? "hello" : 42);
106+
// ^?
107+
```
108+
109+
### Conditional Type Constraints
110+
111+
Often, the checks in a conditional type will provide us with some new information.
112+
Just like with narrowing with type guards can give us a more specific type, the true branch of a conditional type will further constrain generics by the type we check against.
113+
114+
For example, let's take the following:
115+
116+
```ts twoslash
117+
// @errors: 2536
118+
type MessageOf<T> = T["message"];
119+
```
120+
121+
In this example, TypeScript errors because `T` isn't known to have a property called `message`.
122+
We could constrain `T`, and TypeScript would no longer complain:
123+
124+
```ts twoslash
125+
type MessageOf<T extends { message: unknown }> = T["message"];
126+
127+
interface Email {
128+
message: string;
129+
}
130+
131+
type EmailMessageContents = MessageOf<Email>;
132+
// ^?
133+
```
134+
135+
However, what if we wanted `MessageOf` to take any type, and default to something like `never` if a `message` property isn't available?
136+
We can do this by moving the constraint out and introducing a conditional type:
137+
138+
```ts twoslash
139+
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
140+
141+
interface Email {
142+
message: string;
143+
}
144+
145+
interface Dog {
146+
bark(): void;
147+
}
148+
149+
type EmailMessageContents = MessageOf<Email>;
150+
// ^?
151+
152+
type DogMessageContents = MessageOf<Dog>;
153+
// ^?
154+
```
155+
156+
Within the true branch, TypeScript knows that `T` _will_ have a `message` property.
157+
158+
As another example, we could also write a type called `Flatten` that flattens array types to their element types, but leaves them alone otherwise:
159+
160+
```ts twoslash
161+
type Flatten<T> = T extends any[] ? T[number] : T;
162+
163+
// Extracts out the element type.
164+
type Str = Flatten<string[]>;
165+
// ^?
166+
167+
// Leaves the type alone.
168+
type Num = Flatten<number>;
169+
// ^?
170+
```
171+
172+
When `Flatten` is given an array type, it uses an indexed access with `number` to fetch out `string[]`'s element type.
173+
Otherwise, it just returns the type it was given.
174+
175+
### Inferring Within Conditional Types
176+
177+
We just found ourselves using conditional types to apply constraints and then extract out types.
178+
This ends up being such a common operation that conditional types make it easier.
179+
180+
Conditional types provide us with a way to infer from types we compare against in the true branch using the `infer` keyword.
181+
For example, we could have inferred the element type in `Flatten` instead of fetching it out "manually" with an indexed access type:
182+
183+
```ts twoslash
184+
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
185+
```
186+
187+
Here, we used the `infer` keyword to declaratively introduce a new generic type variable named `Item` instead of specifying how to retrieve the element type of `T` within the true branch.
188+
This frees us from having to think about how to dig through and probing apart the structure of the types we're interested in.
189+
190+
We can write some useful helper type aliases using the `infer` keyword.
191+
For example, for simple cases, we can extract the return type out from function types:
192+
193+
```ts twoslash
194+
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
195+
? Return
196+
: never;
197+
198+
type Num = GetReturnType<() => number>;
199+
// ^?
200+
201+
type Str = GetReturnType<(x: string) => string>;
202+
// ^?
203+
204+
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
205+
// ^?
206+
```
207+
208+
When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the _last_ signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types.
209+
210+
```ts twoslash
211+
declare function stringOrNum(x: string): number;
212+
declare function stringOrNum(x: number): string;
213+
declare function stringOrNum(x: string | number): string | number;
214+
215+
type T1 = ReturnType<typeof stringOrNum>;
216+
// ^?
217+
```
218+
219+
## Distributive Conditional Types
220+
221+
When conditional types act on a generic type, they become _distributive_ when given a union type.
222+
For example, take the following:
223+
224+
```ts twoslash
225+
type ToArray<Type> = Type extends any ? Type[] : never;
226+
```
227+
228+
If we plug a union type into `ToArray`, then the conditional type will be applied to each member of that union.
229+
230+
```ts twoslash
231+
type ToArray<Type> = Type extends any ? Type[] : never;
232+
233+
type StrArrOrNumArr = ToArray<string | number>;
234+
// ^?
235+
```
236+
237+
What happens here is that `StrArrOrNumArr ` distributes on:
238+
239+
```ts twoslash
240+
type StrArrOrNumArr =
241+
// ---cut---
242+
string | number;
243+
```
244+
245+
and maps over each member type of the union, to what is effectively:
246+
247+
```ts twoslash
248+
type ToArray<Type> = Type extends any ? Type[] : never;
249+
type StrArrOrNumArr =
250+
// ---cut---
251+
ToArray<string> | ToArray<number>;
252+
```
253+
254+
which leaves us with:
255+
256+
```ts twoslash
257+
type StrArrOrNumArr =
258+
// ---cut---
259+
string[] | number[];
260+
```
261+
262+
Typically, distributivity is the desired behavior.
263+
To avoid that behavior, you can surround each side of the `extends` keyword with square brackets.
264+
265+
```ts twoslash
266+
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
267+
268+
// 'StrArrOrNumArr' is no longer a union.
269+
type StrArrOrNumArr = ToArrayNonDist<string | number>;
270+
// ^?
271+
```

0 commit comments

Comments
 (0)