第十三节:TypeScript 条件类型
条件类型
1. 条件类型
条件类型有助于描述输入和输出类型之间的关系
1.1 条件类型语法
条件类型就是根据一个条件表达式来进行类型检测, 类似于三目运算符
// 语法
T extends U ? X: Y
若 T
是 U
的子类型,则类型为 X
,否则类型为 Y
。若无法确定 T
是否为 U
的子类型,则类型为 X | Y
。
例如:
// 接口
interface Person {
name: string
age: number
}
// 扩展接口
interface Student extends Person{
classId: string
}
// 根据Student是不是继承Person接口来判断返回string类型还是number类型
type Example = Student extends Person ? string : number
// type Example = string
type Example2 = RegExp extends Person ? string : number
// type Example2 = number
示例中,根据接口Student 是否继承Person
接口,来决定类型别名Example
具体是string 类型还是number类型
如不是判断类型的子类型,则返回联合类型
例如:
type Person<Type> = Type extends boolean ? string : number
type StringOrNumber = Person<any>
// type StringOrNumber = string | number
代码解释: 泛型Person
在使用时传入了any
, any
类型可是任何类型, 如果是boolean
, 则返回string类型
, 如果不是boolean
,则返回number
类型, 此时TypeScript就不好抉择了, 直接返回string | number
的联合类型
1.2 条件类型与泛型一起使用
条件类型可能不会立即有用, 但是条件类型的强大之处在于他们和泛型一起使用
例如,我们采用以下createLabel
功能
// 定义id接口
interface IdLabel{
id: number
}
// 定义name接口
interface NameLabel {
name: string
}
// 定义重载更具入参决定返回类型
function createLabel(id:number):IdLabel;
function createLabel(name:string):NameLabel;
function createLabel(nameOrId:string | number):NameLabel | IdLabel;
function createLabel(nameOrId:string | number):NameLabel | IdLabel{
if(typeof nameOrId === 'string'){
return {name:nameOrId}
}else{
return {id:nameOrId}
}
}
createLabel的这些重载描述了一个JavaScript函数, 该函数根据其输入的类型进行选择
请注意:示例中我们必须创建三个重载, 一个用于确定的每种情况(string 或者 number)类型, 一个用于最一般的情况(string | number)联合类型
相反的,我们可以将该逻辑编写为条件类型
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel
首先确定的泛型类型T
限定必须继承number | string
, 然后在根据传入的泛型更详细的是否extends
继承number
类型
,如果为true 则返回IdLabel
接口, 否则返回NameLabel
接口
此时我们就可以使用该条件类型将重载简化为没有重载的单个函数
例如:
function createLabel<T extends string | number>(nameOrId:T):NameOrId<T>{
throw "unimplemented";
}
const namelabel = createLabel('hello')
// const namelabel: NameLabel
const idlabel = createLabel(10)
// const idlabel: IdLabel
const nameidlabel = createLabel(Math.random() > 0.5 ? 'hello' : 42)
// const nameidlabel: NameLabel | IdLabel
2. 条件类型约束
通常, 条件类型的检查会为我们提供一些新信息, 就像使用类型保护缩小类型范围, 可以给我们提供一个更具体的类型一样.
条件类型的真正分支将会进一步限制我们检查类型的泛型
2.1 泛型继承约束
例如.让我们采取以下措施
type MessageOf<T> = T['message']
// 报错: message 无法用于索引类型T
在例子中, TypeScript出错的原因在于T
类型不知道有一个message
的属性.
因此我们可以通过extends
约束以下T
类型, 这样TypeScript就不会在抱怨了
也就是说泛型的传入必须满足具有message
属性
例如:
type MessageOf<T extends {message: unknown}> = T['message']
interface Email {
message: string
}
type EmailMessage = MessageOf<Email>
// type EmailMessage = string
但是,这样的约束会带来另外一个问题, 那就是MessageOf
类型不能使用没有message
属性的类型,例如
interface Dog{
name: string
}
type DogMessage = MessageOf<Dog>
// 报错: 类型Dog 不满足约束 {message:unknown}
// 类型Dog 缺少`message`属性, 但是类型{message:unknown} 需要该属性
那么如果我们想MessageOf
采用任何类型, 并且在message
属性不可用时, 默认为never
, 那该怎么办呢
2.2 条件约束
我们可以尝试移出约束,并使用条件类型来做到这一点
type MessageOf<T> = T extends {message: unknown } ? T['message'] : never;
interface Email {
message: string
}
interface Dog{
name: string
}
type EmailMessage = MessageOf<Email>
// type EmailMessage = string
type DogMessage = MessageOf<Dog>
// type DogMessage = never
在条件为true的分支中,TypeScript知道T
类型会有一个message
属性, 为false的分支中直接返回never
类型
作为另外一个示例, 我们还可以编写一个名为Flatten的类型别名, 用于将数组类型展平, 获取数组元素的类型,如果不是数组类型则不理, 传入什么类型返回什么类型
type Flatten<T> = T extends any[] ? T[number] : T
type Str = Flatten<string[]>
// type Str = string
type Num = Flatten<number>
// type Num = number
示例中, 当Flatten 给定一个数组类型是, 它会通过索引访问number
来获取string[]
的元素类型. 并返回
如果给定的不是数组类型, 则返回给定的类型
3. 在条件类型中推断
条件类型为我们提供了一种方法就是使用infer
关键字来推断我们在真实分支中类型,
例如:我们可以推断数组元素的类型,而不是像之前一样使用索引访问类型, 并手动的获取 元素类型
type Flatten<T> = T extends Array<infer Item> ? Item : T
在这里,我们使用了infer
关键字声明性地引入了一个新的泛型类型变量Item
, 而不是在条件在条件为true
的分支中通过索引访问数组元素的类型. 这使我们不必考虑如何挖掘和探索我们感兴趣的类型结构
也可以使用infer
关键字编写一些有用的辅助类型别名,
例如,对于简单的情况,我们可以从函数类型中提取返回类型:
// 获取函数返回类型
type GetReturnType<T> = T extends (...arg:never[]) => infer Return ? Return : never
type Num = GetReturnType<() => number>
// type Num = number
type Str = GetReturnType<(x:string) => string>
// type Str = string
type Bool = GetReturnType<(a:boolean, b:boolean) => boolean[]>
// type Bool = boolean[]
4. 分布式条件类型
当条件类型作用于泛型时, 它们给定联合类型时变得可分配
例如,采用如下措施
type ToArray<Type> = Type extends any ? Type[] : never;
如果我们将联合类型插入ToArray
,那么条件类型将应用于该联合的每个成员。
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>;
// type StrArrOrNumArr = string[] | number[]
这里发生的情况是StrArrOrNumArr
分布在:
string | number;
并将联合的每个成员类型映射到有效的内容:
ToArray<string> | ToArray<number>;
这给我们留下了:
string[] | number[];
通常,分配性是期望的行为。extends
为避免这种行为,您可以用方括号将关键字的每一侧括起来。
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
// 'StrArrOrNumArr' is no longer a union.
type StrArrOrNumArr = ToArrayNonDist<string | number>;
type StrArrOrNumArr = (string | number)[]