简述
前面介绍了typeof和keyof操作符,下面继续讨论Typescript的类型操作
三、in操作符
in 操作符用于遍历目标类型的属性key值。类似 for .. in 的机制。一般结合[]一起使用。
1. 遍历枚举类型(enum)
enum E1 {
A,
B,
C
}
type TE = {
[P in E1]: string
}
/**
* type TE = {
0: string;
1: string;
2: string;
}
* /
2. 遍历联合类型
in操作符还可以用来遍历联合类型,生成符合目标的映射类型
type Property = 'name' | 'age' | 'phoneNum';
type PropertyMap = {
[key in Property]: string;
}
/**
* type PropertyMap = {
name: string;
age: string;
phoneNum: string;
}
*/
3. 可以结合keyof一起使用
前面讲过,keyof可以获取类型key值的联合类型,结合in操作符可以创建映射类型
let colors = {
red: 'Red',
green:'Green',
blue:'Blue'
}
type Colors = typeof colors
type Partial<T> = {
[P in keyof T]?: T[P]
}
type ColorMap1 = Partial<Colors>
/* 可以得到
* type ColorMap1 = {
red?: string;
green?: string;
blue?: string;
}
*/
4. 结合基础类型使用
in 操作符还可以用于基础类型(string, number, symbol)
type StringKey = {
[key in string]: any;
}
/**
* type StringKey = {
* [x: string]: any;
* }
*/
type NumberKey = {
[key in number]: any;
}
/**
* type NumberKey = {
* [x: number]: any;
* }
*/
in操作符结合[]一起使用,可以进行遍历类型操作,创建新的类型。这是面向Type编程重要基础。
四、 extends操作符
1. extends操作符可以用类于或者类型继承
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
interface Person {
name: string;
age: number;
}
interface Player extends Person {
item: 'ball' | 'swing';
}
keyof Player // 'name' | 'age' | 'item'
2. extends可以用作类型范围限制
extends操作符结合泛型使用时,可以用作类型范围限制。例如前面讨论keyof操作符,声明的getProp函数。这里限制了K的取值范围必须是keyof T,也即类型T的key值的联合类型或者其子集
function getProp<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
另外,Typescript工具库内置了很多类型操作,比如Pick筛选,就用到了extends操作符做类型限制
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
注:lib.es5.d.ts中内置了很多有用的类型操作工具,我们将在后面章节详细讨论
3. extends操作符用于条件类型
typescript 2.8引入了条件类型表达式,类似于三元运算符。格式如下例,表示如果T包含的类型是U包含的类型的 ‘子集’,那么取结果X,否则取结果Y。
T extends U ? X : Y
某种意义上,extends为类型操作提供了条件判断能力,这很重要。
type NonNullable<T> = T extends null | undefined ? never : T; // 如果泛型参数 T 为 null 或 undefined,那么取 never,否则直接返回T。
let demo1: NonNullable<number>; // => number,因为number不是null | undefined的子集
let demo3: NonNullable<undefined>; // => never,因为undefine是null | undefined的子集
4. 条件类型支持嵌套
类似于三元运算符,条件类型支持嵌套,按照顺序依次执行
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<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"
5. 分布式条件类型
在条件类型T extends U ? X : Y 中,当泛型参数 T 取值为 A | B | C 时,且A\B\C均为裸类型时,这个条件类型可以拆解为(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y),这就是分布式条件类型
裸类型表示没有被包裹(Wrapped) 的类型,(如:Array、[T]、Promise 等都不是裸类型),简而言之裸类型就是未经过任何其他类型修饰或包装的类型。\
例如:
type T10 = TypeName<string | (() => void)>; // "string" | "function"
type T12 = TypeName<string | string[] | undefined>; // "string" | "object" | "undefined"
type T11 = TypeName<string[] | number[]>; // "object"
四、infer操作符
1. infer操作符可以用来声明一个待推断的类型变量
条件类型表达式中,可以使用infer关键字来声明一个待推断的类型变量。且infer只能结合extends在条件类型中使用。例如,内置工具类型ReturnType,作用是用来推断函数的返回值类型:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
- extends限制泛型输入必须是函数类型
- 如果满足条件,infer将函数返回结果存储到一个类型变量R当中,否则返回通用类型any
很显然,这里巧妙的利用了Typescript推断类型机制。最终实现推断函数返回类型的目的。看下面实例,会更加直观
let add = (a: number, b: number) => a + b
type RT = ReturnType<typeof add>
type T0 = ReturnType<() => string> // string
type T1 = ReturnType<(s: string) => void> // void
type T2 = ReturnType<<T>() => T> // unkonwn
当然,infer同样可以用在条件类型嵌套里面
type Unpacked<T> =
T extends (infer U)[] ? U :
T extends (...args: any[]) => infer U ? U :
T extends Promise<infer U> ? U :
T;
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
2. infer可以用在条件类型不同声明类型的位置
上面例子中,infer分别用在几个不同的位置。说明只要满足条件类型,infer可以用在不同声明类型的位置,或者推断类型的位置。例如,前面讲过的infer可以返回值的位置上,当然还可以用在参数、泛型声明、属性类型等位置
- 数组类型推断
type ArrayType<T extends any[]> = T extends (infer U)[] ? U : any
type A1 = ArrayType<string[]> // string
let arr = [1,2,3]
type A2 = ArrayType<typeof arr> // number
- Promise类型推断
type PromiseType<T extends Promise<any>> = T extends Promise<infer U> ? U : any
type P1 = PromiseType<Promise<string>> // string
type P2 = PromiseType<Promise<number>> // string
- 参数类型推断
type ParamsType<T extends (...args:any)=>any> = T extends (...args: infer U) => any ? U : never
let add = (a: number, b: number) => a + b
type P1 = ParamsType<typeof add> // [a:number, b:number]
3. 借助infer实现类型转换
基于infer可以声明待推断类型,可以实现类型转换,例如联合类型(Union)、交叉类型(Intersection)、元组(Tuple)之间的相互转换
- 元组转联合类型
元组其实就是元素类型不同的数组。这样描述,虽然有些笼统,但有助于我们理解和使用元组。在一定条件下,可以把元组当做数组的子集,同时可以推断Tuple extends Array表达式结果为真。利用这个特性,infer可以声明并存储一个推断类型,以此达到元组转联合类型的目的
type TupleToUnion<T> = T extends (infer U)[] ? U : never
type T1 = [string, number]
type U1 = TupleToUnion<T0> // string | numbert
- 联合类型转交叉类型
下面是一个联合类型转交叉类型的泛型工具。我们一起分析一下实现原理,从而加深对infer操作符的理解。
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type TAB = UnionToIntersection<T1 | T2> // T1 & T2
type T = UnionToIntersection<string | number> // string & number
当传入条件类型T1 | T2时: 第一步:(U extends any ? (k: U) => void : never) 会把 union 拆分成 (T1 extends any ? (k: T1) => void : never) | (T2 extends any ? (k: T2)=> void : never),即是得到 (k: T1) => void | (k: T2) => void。
第二步:(k: T1) => void | (k: T2) => void extends ((k: infer I) => void) ? I : never,根据逆变特性,可以推断出I为T1的子集,也是T2的子集,可以推断I是T1和T2交叉类型T1&T2。
需要注意的是,3.6版本以后,空的交叉类型,会被进一步推断为never。见这里
type T1 = 'a' & 'b'; // never
type T2 = { a: string } & null; // never in strict mode, null in non-strict mode
type T3 = { a: string } & undefined; // never in strict mode, undefined in non-strict mode
type T4 = string & number; // never
type T5 = number & object; // never
type T6 = symbol & string; // never
type T7 = void & string; // never
另外,上面联合类型转交叉类型代码中,包含两层条件类型嵌套,其中还涉及分布式条件类型以及逆变与协变的概念。前面已经讨论过分布式条件类型,下面说一下协变和逆变。 协变和逆变讨论的是子类型的问题。具体来说比较复杂,相关概念可以看这里。这里我们只关注最终的结论。
如果A是B的子集(子类型)
- 返回值类型是协变的,即(arg:T)=>A 是 (arg:T)=>B的子集
- 参数类型是逆变的,即(arg:B)=>T 是 (arg:A)=>T的子集
我们用代码描述上述结论:
type Type0 = A extends B ? true : false // true
// 协变
type Type1 = ((arg: any) => A) extends ((arg: any) => B) ? true : false // true
// 逆变
type Type2 = ((arg: B) => void) extends ((arg: A) => void) ? true : false // true
因此,在infer关键字声明的待推断类型,推断其类型时,会遵循下面的原则: 如果(arg:any)=>A 是 (arg:any)=>B的子集成立,那么A是B的子集 如果(arg:A)=>void 是 (arg:B)=>void的子集成立,那么B是A的子集
至此,Typescript几个主要类型操作符都已经介绍完毕。其中穿插涉及到了一些概念,如泛型、联合类型、交叉类型、映射类型、条件类型、逆变与协变等。下面一章将从重点介绍这些高级类型开始,结合泛型和上面介绍的操作符、以及相关用例,继续讨论面向Type编程的相关操作