高级类型

  • 交叉类型
  • 联合类型
  • 索引类型
  • 映射类型
  • 条件类型

Typescript高级类型 - 图1

TS的交叉类型

将多个类型合并成为一个新的类型,新类型具有所有类型的特性
语法:typeA & typeB

可用于扩展类型,让原类型有更多属性

  1. // 交叉类型
  2. interface InterfaceA{
  3. methodA(): void;
  4. }
  5. interface InterfaceB{
  6. methodB(): void;
  7. }
  8. let aAndb:InterfaceA & InterfaceB = {
  9. methodA(){},
  10. methodB(){}
  11. }
  12. console.log(aAndb)

TS的联合类型

联合类型

语法:typeA | typeB , 或者使用enum枚举定义类型合集

  1. interface JavaInterface {
  2. helloJava(): void;
  3. build(): void;
  4. }
  5. interface JsInterface {
  6. helloJavaScript(): void;
  7. build(): void;
  8. }
  9. class Java implements JavaInterface {
  10. helloJava() {
  11. console.log("Hello Java")
  12. }
  13. build(){
  14. console.log("build")
  15. }
  16. }
  17. class JavaScript implements JsInterface {
  18. helloJavaScript() {
  19. console.log("Hello JavaScript")
  20. }
  21. build(){
  22. console.log("build")
  23. }
  24. }
  25. // ts类型保护, 定义联合类型的一种方式
  26. const enum Type { Strong, Week };
  27. // 定义联合类型的另一种方式,更常用
  28. // type tp = Strong | Week
  29. function getLanguage(type: Type){
  30. let lang = type === Type.Strong ? new Java() : new JavaScript();
  31. return lang
  32. }
  33. // getLanguage方法中lang对象为联合类型,此时lang的类型不能确定是Java还是JavaScript
  34. // 只能使用两个对象共有的属性/方法,如:build();
  35. // 此时调用helloJava或helloJavaScript则报错

可区分的联合类型

如果函数参数的类型是多个类型的联合类型,且多个类型间有一个共用属性,可以利用共用属性,创建出不同的类型保护区块

利用共用属性,创建出类型保护

  1. interface Square {
  2. kind: "Square";
  3. side: number;
  4. }
  5. interface Rectangle {
  6. kind: "Rectangle";
  7. width: number;
  8. height: number;
  9. }
  10. interface Circle{
  11. kind: "Circle";
  12. radius: number;
  13. }
  14. type Shape = Square | Rectangle | Circle;
  15. // 由于此时没有设置Circle类型的判断,如果s为Circle类型则会返回undefined。
  16. // TS报错:Function lacks ending return statement and return type does not include 'undefined'.
  17. function area(s: Shape): number {
  18. switch (s.kind){
  19. case 'Square':
  20. return s.side * s.side
  21. case 'Rectangle':
  22. return s.height * s.width
  23. }
  24. }

类型never类型,在switch中,将未捕获到的类型赋值给never,检查s是否是never类型。

  1. function area(s: Shape) {
  2. switch (s.kind){
  3. case 'Square':
  4. return s.side * s.side
  5. case 'Rectangle':
  6. return s.height * s.width
  7. // 当缺少对Circle类型判断时,说明可以进入到default,s是Circle类型,并不是设置的never类型
  8. // TS报错:Argument of type 'Circle' is not assignable to parameter of type 'never'.
  9. default:
  10. return ((e: never) => {
  11. throw new Error(e)
  12. })(s)
  13. }
  14. }

正确的设置switch分支处理,对联合类型的所有类型都进行覆盖

  1. function area(s: Shape) {
  2. switch (s.kind){
  3. case 'Square':
  4. return s.side * s.side;
  5. case 'Rectangle':
  6. return s.height * s.width;
  7. case 'Circle':
  8. return (s.radius**2) * Math.PI;
  9. default:
  10. return ((e: never) => {
  11. throw new Error(e)
  12. })(s)
  13. }
  14. }

TS索引类型

索引类型三个概念:

  • 查询操作符 keyof
    • keyof T:表示类型T,所有公共属性字面量的联合类型
  • 访问操作符 T[K]
    • T[K]:表示对象T的属性K所表示的类型
  • 范型约束 extends
    • T extends U:表示范型变量可以通过继承某个类型,获得属性

      索引类型查询

      TypeScript 允许我们遍历某种类型的属性,并通过 keyof 操作符提取其属性的名称。keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型

      keyof 与 Object.keys 略有相似,只不过 keyof 取 interface 的键。 除接口外,keyof 也可以用于操作类

keyof T:表示类型T的所有属性的字面量【即key】的联合类型
typeof obj:typeof可以获取值的类型

  1. // 定义一个接口Obj含有属性a,b
  2. interface obj {
  3. age: number
  4. name: string
  5. }
  6. // 定义变量key,类型为keyof obj --转化后,key字面量的联合类型--> "age" | "name"
  7. let key: keyof obj = "name" // key的类型为 "age" | "name"
  8. // keyof操作类
  9. class Person {
  10. name: string = "北鸟南游";
  11. age: number = 1;
  12. }
  13. let sname: keyof Person; // sname: "name" | "age"
  14. sname = "name";

keyof 操作符除了支持接口和类之外,它也支持基本数据类型:

  1. let K1: keyof boolean; // let K1: "valueOf"
  2. let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...
  3. let K3: keyof symbol; // let K1: "valueOf"

另外一种特殊情况,定义的接口属性不定。

  1. interface Foo {
  2. [props: string]: string;
  3. }

此时Foo接口被认定为属性字段全部为string,由于js可以通过数字和字符串访问对象属性,因此 keyof Foo 的结果是string | number;

  1. let f:key Foo = 123; //此时也不报错

image.png
如果想通过一个方法getKey获取对象里的属性值,通常的写法

  1. let Boy = {
  2. name:"xm",
  3. age:10
  4. }
  5. function getKeys(o:object, k: string){
  6. return o[k];
  7. }

此时在return o[k]时,TS会报错,类型检验无法通过。
image.png
为了解决这个错误,可以使用范型加索引类型

  1. let Boy = {
  2. name:"xm",
  3. age:10
  4. }
  5. function getKeys<T extends object, K extends keyof T>(o:T, name:K): T[K]{
  6. return o[name];
  7. }
  8. console.log(getKeys(Boy, "name"));

索引类型访问

语法:T[K],表示对象T的属性K的类型, 和访问对象的属性值类似

  1. interface Person {
  2. age: number;
  3. }
  4. let p:Person['age']; //p:number,p的类型为数字
  5. type Man = { name:'man'; age:12}
  6. let m:Man['name']; //m的类型为string

范型约束 extends

T extends U:范型T可以继承对象U的属性

  1. function prop<T extends object, K extends keyof T>(obj: T, keys: K): T[K]{
  2. return obj[keys];
  3. }

TS条件类型

TS提供几种内置的预定义的条件类型

  • Exclude - 用于从类型T中去除不在U类型中的成员【从类型中排除类型, 和omit不同】
  • Extract - 用于从类型T中取出可分配给U类型的成员【从类型中提取类型,和pick不同】
  • NonNullable - 用于从类型T中去除undefined和null类型
  • ReturnType - 获取函数类型的返回类型
  • InstanceType - 获取构造函数的实例类型

    条件类型

    条件类型是由条件表达式决定的类型,使类型具有不唯一性,增加TS语言灵活性。内置类型主要是通过三元表达式和 范型约束判断实现

T extends U ? X : Y;
如果类型T可被赋值给类型U,那么结果类型是X类型,否则是Y类型。

  1. // 条件类型
  2. type TypeName<T> =
  3. T extends string ? 'string' :
  4. T extends number ? 'number' :
  5. T extends boolean ? 'boolean' :
  6. T extends undefined ? 'undefined' :
  7. T extends Function ? 'Function' :
  8. 'object'
  9. // 定义类型T1为条件类型,传入参数string,指定t1为string类型
  10. type T1 = TypeName<string>
  11. // 定义类型T2为条件类型,传入参数string[]
  12. type T2 = TypeName<string[]>

image.png
T2传入的是数组,不在所属的条件中,则T2的类型为最后的object。
image.png

分步式条件类型

T是联合类型,结果类型变为多条件类型的联合类型

(A | B) extends U ? X : Y;
将A和B拆解计算后再联合
(A extends U ? X : Y) | (B extends U ? X : Y)

  1. type T3 = TypeName<string | string[]>

分步式条件类型应用

对类型进行过滤

如果T可以被赋值给U,结果类型为never类型,否则为T类型

  1. // 如果T可以被赋值给U,结果类型为never类型,否则为T类型
  2. type Diff<T, U> = T extends U ? never : T
  3. type T4 = Diff<'a' | 'b' | 'c', 'a' | 'x'>

image.png

拆解逻辑分析

Diff会被拆解为多个条件类型的联合类型

  1. type Diff<T, U> = T extends U ? never : T; //和内置条件类型Exclude一样
  2. type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'>
  3. // 拆解分析:
  4. // Diff<'a', 'a' | 'e'> | Diff<'b', 'a' | 'e'> | Diff<'c', 'a' | 'e'>
  5. // never | 'b' | 'c'
  6. // 'b' | 'c'
  1. 先判断a是否可以被赋值给这个字面量联合类型’a’ | ‘e’,答案是可以的,所以返回never
  2. 继续,因为b不可以被赋值给字面量联合类型’a’ | ‘e’,所以返回b
  3. 继续,c不可以被赋值给’a’ | ‘e’,所以返回c
  4. 最后,never和b,c的联合类型为’b’ | ‘c’

可以实现从类型T中移除不需要的类型,如undefined和null,定义一个NotNull,从T中过滤掉undefined和null

  1. // Diff扩展:从T中过滤掉undefined和null
  2. type NotNull<T> = Diff<T, undefined | null>
  3. // 过滤掉undefined和null,T5的类型就变成了string和number
  4. type T5 = NotNull<string | number | undefined | null>

image.png
上述实现过程,在TS中有内置的方法:Extract和Exclude和NonNullable以及ReturnType和InstanceType;

  • Exclude作用是从类型T中过滤掉可以赋值给类型U的类型
  • Extract作用是可以从类型T中抽取出可以赋值给U的类型

    1. // Extract<T, u>和Exclude<T, U>
    2. type T6 = Extract<'a' | 'b' | 'c', 'a' | 'e'>
    3. type T7 = Exclude<'a' | 'b' | 'c', 'a' | 'e'>

    image.pngimage.png

    infer关键字 【推断】的使用

  • infer 用来推断值的类型,放在什么位置就是推断哪里的结果,多用在函数的参数或返回值中

  • infer要配合 extends 关键字使用,能自动推断出结果

    定义的函数类型 (…args: any[]) => any

  1. function getPos(x:number, y:number){
  2. return {posX: x+'px', posY: y+ 'px'}
  3. }
  4. /* 获取函数参数类型 infer P 推导函数参数类型 */
  5. type MyParameters<T extends (...args: any[]) => any> =
  6. T extends (...args: infer P) => any // infer P用来临时存放函数参数类型,如果有值则是P,否则是any
  7. ? P : any;
  8. // Parameters 推导出 参数的类型
  9. type getPosParamsType = MyParameters<typeof getPos>; // type getPosParamsType = [x: number, y: number]
  10. /* 获取函数返回值类型 infer R 推导函数返回值类型 */
  11. type MyReturnType<T extends (...args: any[]) => any> =
  12. T extends (...args: any) => infer R // infer R用来临时存放函数返回值类型,如果有值则是R,否则是any
  13. ? R : any;
  14. // ReturnType 推导出 返回值的类型
  15. type getPosType = MyReturnType<typeof getPos> // type getPosType = { posX: string; posY: string; }

使用infer提取类型的一部分

提取数组中的第一个

  1. type first<T extends unknown[]> = T extends [infer X, ...infer Y] ? X : any;
  2. // 获取元组的第一个值
  3. type res = first<[1,2,3]>; // type res = 1

注意,第一个 extends 不是条件而是约束,条件类型是 extends ? : ,表示约束类型参数只能是数组类型

TS中内置条件类型源码分析

Extract和Exclude的源码分析
  • Extract:从类型中,提取指定类型
  • Exclude:从类型中排除指定类型 ```typescript /* Exclude from T those types that are assignable to U / type Exclude = T extends U ? never : T;

/* Extract from T those types that are assignable to U / type Extract = T extends U ? T : never;

/* 使用 / type originType = string | number | boolean; type excludeType = Exclude type extractType = Exclude

  1. <a name="kW1IK"></a>
  2. ##### NonNullable源码,排除掉所有空的类型
  3. ```typescript
  4. // NonNullable,排除掉所有空的类型
  5. type T9 = NonNullable<string | number | undefined | null>

image.png

  1. /** Exclude null and undefined from T */
  2. type NonNullable<T> = T extends null | undefined ? never : T;

Parameters 获取函数参数类型
  1. function getPos(x:number, y:number){
  2. return {posX: x+'px', posY: y+ 'px'}
  3. }
  4. /* 获取函数参数类型 infer P 推导函数参数类型 */
  5. type MyParameters<T extends (...args: any[]) => any> =
  6. T extends (...args: infer P) => any // infer P用来临时存放函数参数类型,如果有值则是P,否则是any
  7. ? P : any;
  8. // Parameters 推导出 参数的类型
  9. type getPosParamsType = MyParameters<typeof getPos>;
  10. // 转化后getPosParamsType = [x:number, y:number]

ReturnType 获取函数返回值的类型
  1. // ReturnType<T>获取函数返回值的类型
  2. type T8 = ReturnType<() => string>

image.png
TS中ReturnType源码:【使用 infer 关键字,为推断的意思,可以js中let/var定义变量类似】

  1. /** Obtain the return type of a function type */
  2. type ReturnType<T extends (...args: any) => any>
  3. = T extends (...args: any) => infer R ? R : any;

实现分析:
T extends (…args: any) => any : ReturnType要求参数 T 可以赋值给一个函数,这个函数有任意的参数,返回值类型也是任意的
由于函数返回值类型不确定,这里使用infer关键字,表示待推断,延迟推断,需要根据实际的情况确定
infer R ? R : any
如果实际类型是R,那么结果类型就是R,否则返回值类型就是any

InstanceType:返回构造函数的类型
  1. class ClassDemo {
  2. constructor(x:number,y:string){}
  3. }
  4. // typeof ClassDemo获取类的类型
  5. type CType = InstanceType<typeof ClassDemo> ;

image.png
TypeScript源码的实现

  1. /** Obtain the return type of a constructor function type */
  2. type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

ConstructorParameters返回构造函数参数的类型

ConstructorParameters 传入一个将类转化后的类型参数T

  1. /** 源码 */
  2. type MyConstructorParameters<T extends new (...args: any[]) => any>
  3. = T extends new (...args: infer C) // 使用infer C对类的构造函数中参数进行推断
  4. => any ? C : any;
  5. /** 使用 */
  6. class ClassDemo {
  7. constructor(x:number,y:string){}
  8. }
  9. // typeof ClassDemo获取类的类型
  10. type CType = MyConstructorParameters<typeof ClassDemo>;
  11. //转化后, type CType = [x: number, y: string]
  12. let ct: CType = [1,'2']

TS的高级类型-映射类型

映射类型:TS允许将一个类型映射成另外一种类型

有以下Readonly、Partial、Pick三种同源映射,和Record非同源映射。

Readonly 只读

将一个接口的所有属性映射为只读

  1. interface Obj{
  2. a:number;
  3. b:string;
  4. c:boolean;
  5. }
  6. type ReadonlyObj = Readonly<Obj>
  7. /* TS编译之后
  8. type ReadonlyObj = {
  9. readonly a: number;
  10. readonly b: string;
  11. readonly c: boolean;
  12. } */
  13. let ReadonlyObjDemo:ReadonlyObj = {
  14. a:1,
  15. b:"readonly",
  16. c:true
  17. }
  18. ReadonlyObjDemo.a = 3;
  19. //error Cannot assign to 'a' because it is a read-only property.
  1. type Readonly<T> = {
  2. readonly [K in keyof T] : T[K]
  3. }

Required 必选

Requied:将类型T中的所有属性变为必填项

  1. interface requiredType{
  2. name: string;
  3. age?: number;
  4. isBoy?: boolean;
  5. }
  6. // 会将age和isBoy变为必填属性
  7. let requiredObj: Required<requiredType> = {
  8. name: "北鸟南游",
  9. age: 18,
  10. isBoy: true
  11. }
  1. type Partial<T> = {
  2. [P in keyof T] -?: T[P]; // 通过 -? 将可选属性变为必填属性
  3. };

Partial 可选

将一个接口的所有属性映射为可选.

  1. interface Obj{
  2. a:number;
  3. b:string;
  4. c:boolean;
  5. }
  6. type PartialObj = Partial<Obj>
  7. /* TS编译之后
  8. type PartialObj = {
  9. a?: number | undefined;
  10. b?: string | undefined;
  11. c?: boolean | undefined;
  12. } */
  13. // a/b/c三个属性都是可选的
  14. let PartialObjDemo:PartialObj = {
  15. a:100
  16. }
  1. /**
  2. * Make all properties in T optional
  3. */
  4. type Partial<T> = {
  5. [P in keyof T]?: T[P];
  6. };
  7. type MyPartial<T> = { [K in keyof T]?: T[K]};
  8. type partObj = MyPartial<Obj>;

定义深层Partial, DeepPartial

将内部是对象类型继续执行Partial

  1. interface Obj{
  2. a:number;
  3. b:string;
  4. c:boolean;
  5. d: Bar;
  6. }
  7. interface Bar {
  8. x:number;
  9. y:string;
  10. }
  11. type DeepPartial<T> = { [K in keyof T]? :
  12. T[K] extends object ? //使用extends判断T[K]是否是对象
  13. MyPartial<T[K]> : T[K]}
  14. type DeepObj = DeepPartial<Obj>;
  15. let dpo:DeepObj = {a:1, d: {x:2}}; //这是d赋值几个参数都可以

Pick 摘取部分

摘取对象的一部分属性,形成新类型
语法:

和Extract的不同:

  • Extract是两个类型参数,从类型中挑选类型
  • Pick是从对象的属性中挑选,形成新的类型
  1. interface Obj{
  2. a:number;
  3. b:string;
  4. c:boolean;
  5. }
  6. type PickObj = Pick<Obj, "a" | "b"> //抽取了Obj对象的a、b属性,形成一个新的类型
  7. /* TS编译之后
  8. type PickObj = {
  9. a: number;
  10. b: string;
  11. } */
  12. let pickObjDemo:PickObj = {
  13. a:66,
  14. b:'66'
  15. }
  16. /** MyPick 源码 **/
  17. // 通过 [X in K]循环K中的所有类型
  18. type MyPick<T, K extends keyof T> = { [X in K] : T[X]};
  19. type pickType = MyPick<Obj, "a"> // 转化后 type pickType = { a: number; }

Omit 排除部分属性的类型

排除对象中的一部分属性,Omit

和Exclude的区别

  • Exclude是排除类型中的类型
  • Omit是排除对象上的属性,获取到的类型
  1. type OmitObj = Omit<Obj, "a" | "b"> //排除Obj对象的a、b属性,形成一个新的类型
  2. /* TS编译之后
  3. type OmitObj = {
  4. c: boolean;
  5. } */
  6. let omitObjDemo:OmitObj = {
  7. c:true
  8. }
  1. // 第一步先从对象T中 排除掉设置的属性K Exclude<keyof T, K>
  2. // keyof T是获取对象中的key值,从所有key中排除设置的K
  3. // 第二步,Pick剩下的属性组成的类型
  4. type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
  5. type omitType = MyOmit<Obj, "a">

非同源映射Record,用来描述对象类型

引入新属性,将引入的属性的类型,设置为第二个参数。
语法: Record 将x设置为T类型

  1. /* Record 源码*/
  2. // [P in K] 把K循环一次,
  3. // 然后设置为T类型
  4. type Record<K extends keyof any, T> = {
  5. [P in K]: T;
  6. };
  7. // K为新增的属性,T为属性类型
  8. type RecordObj = Record<"x" | "y", Obj>
  9. // 定义一个对象包含x/y属性,这两个属性的类型为Obj类型
  10. /* TS编译之后
  11. type RecordObj = {
  12. x: Obj;
  13. y: Obj;
  14. } */
  15. let RecordObjDemo:RecordObj = {
  16. x:{
  17. a:1,
  18. b:"x",
  19. c:true
  20. },
  21. y:{
  22. a:2,
  23. b:"y",
  24. c:false
  25. },
  26. }

自定义类型

定义其他常用的类型,类型体操。

Diff 差集类型

获取类型T和类型U的差值,就是类型T中包含的类型,但是U中不包含。

  1. let p1 = {
  2. name: "p1",
  3. age:1,
  4. address: "shanghai"
  5. }
  6. let p2 = {
  7. address: "beijing",
  8. height: 100
  9. }
  10. type Diff<T extends object, U extends object> = Omit<T, keyof U>;
  11. type p1DiffP2 = Diff<typeof p1, typeof p2>; // 差值之后 type p1DiffP2 = { name: string; age: number; }

Inter交集

获取中都有的类型的交集

  1. let p1 = {
  2. name: "p1",
  3. age:1,
  4. address: "shanghai"
  5. }
  6. let p2 = {
  7. address: "beijing",
  8. height: 100
  9. }
  10. // 报错 类型“keyof U”不满足约束“keyof T”,类型U中包含了T中不存在的类型
  11. // type Inter<T extends object, U extends object> = Pick<T,keyof U>;
  12. // 正确写法; 首先确保Pick第二个参数挑选的类型存在于第一个参数,
  13. // Extract<keyof T, keyof U>获取T中包含U中的类型,所以取出的值肯定包含在T中
  14. // Pick源码type Pick<T, K extends keyof T> = { [X in K] : T[X]}
  15. type Inter<T extends object, U extends object> = Pick<T, Extract<keyof T, keyof U>>;
  16. type p1p2Inter = Inter<typeof p1, typeof p2>

MergeType 两个类型的合并

T和U两个类型合并,有重复的类型,以后边的为准。

如果简单使用 & 交叉类型的话,重复出现且类型不一样,就会判断为never

  1. // 两个type类型的合并
  2. // 删除T中包含的U中的类型,剩下的就是T中独有的,然后和U进行合并
  3. type MergeType<T extends object, U extends object> = Omit<T, keyof U> & U
  4. type t1 = {
  5. name: string;
  6. age: number;
  7. height: string;
  8. }
  9. type t2 = {
  10. age: string;
  11. weight: number;
  12. }
  13. // 展开T类型中的类型,组成一个对象
  14. type ComputeType<T> = {[K in keyof T]: T[K]}
  15. type mtype = ComputeType<MergeType<t1, t2>>
  16. /* type mtype = {
  17. name: string;
  18. height: string;
  19. age: string;
  20. weight: number;
  21. } */

类型实践操作

装包、拆包。

  • proxify对对象的属性进行封装,属性后只能获取到get/set属性
  • unProxify对包装的对象拆解,获取到原类型的属性或方法 ```typescript let data = { name: “test”, age:1 } // 可以让代理对象获取到原对象身上的值 type Proxify = {
  1. [K in keyof T]: {
  2. get(): T[K],
  3. set(value:any): void
  4. }

} // function proxify(obj:T): Proxify{ let result = {} as Proxify; for(let key in obj){ let value = obj[key]; result[key] = { get(){ return value }, set(newValue){ value = newValue; } } } return result; } let proxyData = proxify(data);

console.log(proxyData.name.get()) // 这里name后只有get或set属性

function unProxify(obj: Proxify): T{ let result = {} as T; for(let key in obj){ let value = obj[key] result[key] = value.get() } return result; } let afterData = unProxify(proxyData); afterData.name.length; // name后获取的是字符串的属性 afterData.age.toFixed(2) // age后显示的是数字类型的属性 ```