This feature, introduced in TS2.8, makes type definitions more flexible. Note that extends applies to both type and class.
A conditional type is a kind of conditional expression for type relationship detection.
Simple value matching
type Equal<X, Y> = X extends Y ? true : false;
type Num = Equal<1.1>; // true
type Str = Equal<'a'.'a'>; // true
type Boo = Equal<true.false>; // false
Copy the code
It can be easily understood as a ternary expression, but of course it’s not that simple. Let’s dig deeper.
Simple type matching
Start by understanding what “assignable” means, such as this simple and correct type declaration and assignment:
const num: number = 1; // Can be assigned
cosnt str: string = 2; // Cannot be assigned
Copy the code
The judgment logic of the condition type is the same logic as that of “assignable” above:
type isNum<T> = T extends number ? number : string
type Num = InstanceVal<1> // number;
type Str = InstanceVal<'1'> // string;
Copy the code
Nested type match
Just like if statements, they can be nested indefinitely. Get the function type of the value type name based on the value type (this is also the official example) :
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<"a">; // "string"
type T2 = TypeName<true>; // "boolean"
type T3 = TypeName<(a)= > void>; // "function"
type T4 = TypeName<string[] >;// "object"
Copy the code
Determining union types
type A = 'x';
type B = 'x' | 'y';
type Y = A extends B ? true : false; // true
Copy the code
Where all subtypes of union type A exist in union type B, then the condition is satisfied. If we replace the return value with another type definition, we can determine the type we want.
When the union type cannot make a judgment
Let’s write a function type whose logic is simple: return A and return B if given type x.
type AB<T> = T extends 'x' ? 'a' : 'b';
type A = AB<'x'>;
// get type A = 'A'
Copy the code
It looks fine because we passed in an explicit judgment value, which must be true.
Suppose we introduced to uncertain value, such as a joint types’ x ‘|’ y ‘? The judgment logic may be true or false. TypeScript doesn’t know what to do either, so it returns us both values:
type AB<T> = T extends 'x' ? 'a' : 'b';
type All = AB<'x' | 'y'>; // A non-deterministic condition can be 'x' or 'y'
/ / get the type All = 'a' | 'b'.
Copy the code
We get a union type that contains all the returned values.
A delay in parsing the condition type was made.
The additional effect of delaying parsing conditional types
Knowing that the condition type returns all values when it is indeterminate has some additional effects.
Now we return the T type that was passed in, and something interesting happens. And different positions of T produce different effects:
type Other = "a" | "b";
type Merge<T> = T extends "x" ? T : Other; // T equals the matching type and is returned with the Other union type
type Values = Merge<"x" | "y">;
/ / get the type Values = "x" | | "a" "b".
Copy the code
type Other = "a" | "b";
type Merge<T> = T extends "x" ? Other : T; // T is equal to all the additional types except the matching type.
type Values = Merge<"x" | "y">;
/ / get the type Values = "a" | "b" | 'y'.
Copy the code
Using this feature, we can write a common function type like Diff
, a filter function type:
type Filter<T, U> = T extends U ? never : T;
type Values = Filter<"x" | "y" | "z"."x">;
/ / get the type Values = "y" | "z"
Copy the code
Never will be discussed separately. Here we understand that nothing will do.
Conditional types can be combined with mapping types to create a wide range of function types. The website presets advanced types (such as Partial, Readonly, Record, and Pick in Typescript), and then shares all of the officially defined advanced types.
The infer type Inference capability of TypeScript Conditional Types is also provided.