Conditional type
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
type Example1 = Dog extends Animal ? number : string;
type Example1 = number
type Example2 = RegExp extends Animal ? number : string;
type Example2 = string
Conditional types take a form that looks a little like conditional expressions (condition ? trueExpression : falseExpression) in JavaScript:
SomeType extends OtherType ? TrueType : FalseType;
But the power of conditional types comes from using them with generics.
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "unimplemented";
}
let a = createLabel("typescript");
let a: NameLabel
let b = createLabel(2.8);
let b: IdLabel
let c = createLabel(Math.random() ? "hello" : 42);
let c: NameLabel | IdLabel
Conditional Type Constraints
type MessageOf<T extends { message: unknown }> = T["message"];
interface Email {
message: string;
}
type EmailMessageContents = MessageOf<Email>;
type EmailMessageContents = string
However, what if we wanted MessageOf to take any type, and default to something like never if a message property isn’t available? We can do this by moving the constraint out and introducing a conditional type:
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
interface Email {
message: string;
}
interface Dog {
bark(): void;
}
type EmailMessageContents = MessageOf<Email>;
type EmailMessageContents = string
type DogMessageContents = MessageOf<Dog>;
type DogMessageContents = never
As another example, we could also write a type called Flatten that flattens array types to their element types, but leaves them alone otherwise:
type Flatten<T> = T extends any[] ? T[number] : T;
// Extracts out the element type.
type Str = Flatten<string[]>;
type Str = string
// Leaves the type alone.
type Num = Flatten<number>;
type Num = number
Inferring Within Conditional Types
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
? Return
: never;
type Num = GetReturnType<() => number>;
type Num = number
type Str = GetReturnType<(x: string) => string>;
type Str = string
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
type Bools = boolean[]
declare function stringOrNum(x: string): number;
declare function stringOrNum(x: number): string;
declare function stringOrNum(x: string | number): string | number;
type T1 = ReturnType<typeof stringOrNum>;
type T1 = string | number
Distributive Conditional Types
When conditional types act on a generic type, they become distributive when given a union type.
type ToArray<Type> = Type extends any ? Type[] : never;
//If we plug a union type into ToArray, then the conditional type will be applied to each member of that union.
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>;
type StrArrOrNumArr = string[] | number[]
Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
// 'ArrOfStrOrNum' is no longer a union.
type ArrOfStrOrNum = ToArrayNonDist<string | number>;
type ArrOfStrOrNum = (string | number)[]