条件类型

用来帮助我们描述输入类型和输出类型之间的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

一般多搭配泛型使用

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}

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";
}

上面的例子使用了重载来控制输入与输出的关系,但过多的重载不是一件好事
可以通过条件类型简化掉函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

条件类型约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

在条件类型里推断

条件类型提供了 infer 关键词,可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果。通过infer可以避免通过索引类型手动访问每一个变量类型

1
2
3
type Flatten<T> = T extends any[] ? T[number] : T;

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

可以通过infer写一些类型帮助别名

1
2
3
4
5
6
7
8
9
10
11
12
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[]

当从多重调用签名(就比如重载函数)中推断类型的时候,会按照最后的签名进行推断,因为一般这个签名是用来处理所有情况的签名。

1
2
3
4
5
6
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

分发条件类型

当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成 分发的

1
2
3
4
type ToArray<Type> = Type extends any ? Type[] : never;

type StrArrOrNumArr = ToArray<string | number>;
// type StrArrOrNumArr = string[] | number[]

如果你要避免这种行为,你可以用方括号包裹 extends 关键字的每一部分

1
2
3
4
5
6
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;

// 'StrArrOrNumArr' is no longer a union.
type StrArrOrNumArr = ToArrayNonDist<string | number>;
// type StrArrOrNumArr = (string | number)[]

作者

徐云飞

发布于

2022-10-27

更新于

2023-02-05

许可协议

评论