- Description : There is currently no Chinese translation of the latest official documents of TypeScript on the Internet, so there is such a translation plan. Because I am also a beginner in TypeScript, I cannot guarantee that the translation will be 100% accurate. If there are errors, please point out in the comment section;
- translation content : The tentative translation content is TypeScript Handbook , and other parts of the translation document will be added later;
- project address : TypeScript-Doc-Zh , if it helps you, you can click a star~
The official document address of this chapter: Conditional Types
Condition type
At the core of most applications, we need to decide which logic to execute based on the input. The same is true for JavaScript applications, but because the value is easy to introspect (Translator's Note: Introspection refers to the code being able to check itself, access internal properties, and obtain the underlying information of the code), so the specific logic to be executed depends on the type of input data. The condition type can be used to describe the connection between the input type and the output 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
The form of the condition type is a bit like conditional expressions in JavaScript ( condition? True branch: false branch):
SomeType extends OtherType ? TrueType : FalseType;
When extends
can be assigned to the type on the right, the final result is the type in the first branch (true branch), otherwise the type in the second branch (false branch) is obtained.
Only from the above example, the condition type does not seem very useful-even if we don't rely on it, we can know Dog extends Animal
is true, and then select the corresponding number
type or string
type! But if you combine conditional types with generics, it can exert tremendous power.
For example, let's look at the following createLabel
function:
interface IdLabel {
id: number /* 一些属性 */
}
interface NameLabel {
name: string /* 其它属性 */
}
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
throw "unimplemented";
}
createLabel
function uses overloading to select different output types based on different input types. Please note that there are some problems with this:
- If a library has to make the same choice over and over again throughout the API, then this will become very complicated.
- We need to create three overloads: the first two are for specific input types (
string
andnumber
), and the last one is for the most general case (input type isstring | number
). OncecreateLabel
adds new types that can be handled, the number of overloads will increase exponentially.
So might as well as another way, we can encode the logic of the above code into a condition type:
type NameOrId<T extends number | string> = T extends nummber
? IdLabel
: NameLabel;
Then, we can use this conditional type to simplify the original overloaded function into a function without overloading:
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
Condition type constraints
Normally, the check in the condition type will provide us with some new information. Just as the type contraction implemented by type protection can get a more specific type, the true branch of the conditional type can further constrain the generic type by the type we check.
Take the following code as an example:
type MessageOf<T> = T['message'];
//Type '"message"' cannot be used to index type 'T'.
In this code, TypeScript throws an error because it cannot determine whether T
has the message
attribute. We can T
so that TypeScript will no longer report an error:
type MessageOf<T extends { message: unknown }> = T['message'];
interface Email {
message: string;
}
type EmailMessageContents = MessageOf<Emial>;
^
// type EmailMessageContents = string
However, if we want MessageOf
to accept any type, and never
type by default when the message
attribute does not exist, what should we do? We can remove the constraints and then introduce the condition type:
type MessageOf<T> = T extends { message: unknown } ? T['message'] : never
interface Email {
message: string;
}
interface Dog {
bark(): void;
}
type EmialMessageContents = MessageOf<Email>;
^
// type EmialMessageContents = string
type DogMessageContents = MessageOf<Dog>;
^
// type DogMessageContents = never
In the true branch of the condition type, TypeScript knows that T
will have the attribute message
Let's look at another example. We can write a Flatten
, which can flatten the array type into the type of the elements in the array, and retain its original type for non-array types:
type Flatten<T> = T extends any[] ? T[number] : T;
// 提取元素类型
type Str = Flatten<string[]>;
^
// type Str = string
// 保留原类型
type Num = Flatten<number>;
^
// type Num = number
When Flatten
accepts an array type, it will use number
to access by index, thereby extracting the element type string[]
; if it does not accept an array type, it will directly return the given original type.
Inference in condition type
In the above example, we used conditional types to apply constraints and extract the types. Since this kind of operation is so common, the condition type provides a simpler way to accomplish it.
The condition type provides the infer
keyword, which allows us to infer a certain type in the condition and apply it to the true branch. For example, in the above Flatten
function, we can directly infer the type of the array element, instead of "manually" extracting the type of the element through index access:
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
Here, we use the infer
keyword to declaratively introduce a new generic type variable Item
, instead of specifying how to extract the element type T
This saves us from having to think about how to find the structure of the type we are interested in.
We can use the infer
keyword to write some useful tool type aliases. For example, in some simple cases, we can extract the return value type from the function type:
type GetReturnType<Type> = Type extends (...args: never[]) => infer Rerturn
? 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[]
If inference is made in a type with multiple call signatures (such as the type of an overloaded function), then the inference will only be for the last signature (that is, the most common case).
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
Assignable condition types
When the condition type acts on a generic type, if a union type is given, then the condition type at this time is assignable. For example, look at the following code:
type ToArray<Type> = Type extends any ? Type[] : never;
If we toArray
, then the condition type will be applied to each member of the union type.
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>;
^
// type StrArrOrNumArr = string[] | number[]
StrArrOrNumArr
in the following union types:
string | number
Then effectively map each member of the union type to the following array:
ToArray<string> | ToArray<number>;
The result is the following array:
string[] | number[];
Normally, this is the desired behavior. If you want to circumvent this behavior, you can extends
the left and right sides of the 061aad9c1e4009 keyword with a square bracket.
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
// 'StrArrOrNumArr' 不再是一个联合类型
type StrArrOrNumArr = ToArrayNonDist<string | number>;
^
// type StrArrOrNumArr = (string | number)[]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。