TS高级类型

110 阅读6分钟

keyof

返回对象中所有key的联合类型。

keyof 对象

得到该对象中所有的key

type Dog = { name: string, age: number };
type D = keyof Dog;// name | age

遇到索引签名

遇到索引签名返回所有索引类型。

type Doggy = {  [y:string]: boolean };
type doggy = keyof Doggy;// string | number 因为js中对象属性类型默认转化为字符串type Dog = {  [y:number]: number  };
type dog = keyof Dog;  //type dog = numbertype Doggy = {  [y:string]: unknown, [x:number]: boolean};
type doggy = keyof Doggy; //type doggy = string | number

any

// type T = string | number | symbol
type T = keyof any;

非对象

返回其类型对应的对象的所有key

image.png

in 不可遍历对象

in右侧一般跟着联合类型in可对联合类型进行迭代。

type Animals = 'pig' | 'cat' | 'dog'
type animals = {
    [key in Animals]: string
}
// type animals = {
//     pig: string; //第一次迭代
//     cat: string; //第二次迭代
//     dog: string; //第三次迭代
// }type T2 = {
    [key in any]: string
    // string: string number: string Symbol: string
}

extends

接口继承

  interface T1 {
    name: string
  }
  
  interface T2 {
    sex: number
  }
  
  // 多重继承,逗号隔开
  interface T3 extends T1,T2 {
    age: number
  }
  
  // 合法
  const t3: T3 = {
    name: 'xiaoming',
    sex: 1,
    age: 18
  }

条件判断

extends前面的类型能够赋值给extends后面的类型,即左边操作符是否能满足右边操作符

  // 示例2
  interface A1 {
    name: string
  }
​
  interface A2 {
    name: string
    age: number
  }
  // A的类型为string
  type A = A2 extends A1 ? string : number
  
  const a: A = 'this is string'
// 上面的示例中,A2比A1多了age属性,A1相比A2的限制更少,能满足A2,则A1类型的值可以赋值给A2类型的值,判断为真。

特例

对于使用extends关键字的条件类型(即上面的三元表达式类型),如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。分配律是指,将联合类型的联合项拆成单项,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果。

  type A1 = 'x' extends 'x' ? string : number; // string
// 因为'x'并不能满足'y'
  type A2 = 'x' | 'y' extends 'x' ? string : number; // number
  
  type P<T> = T extends 'x' ? string : number;
  type A3 = P<'x' | 'y'> // <'x'> ? string : number
      // <'y'> ? string : number
      // 结果为string | number

该例中,extends的前参为T,T是一个泛型参数。在A3的定义中,给T传入的是'x'和'y'的联合类型'x' | 'y',满足分配律,于是'x'和'y'被拆开,分别代入P<T>

P<'x' | 'y'> => P<'x'> | P<'y'>

'x'代入得到

'x' extends 'x' ? string : number => string

'y'代入得到

'y' extends 'x' ? string : number => number

然后将每一项代入得到的结果联合起来,得到string | number

总之,满足两个要点即可适用分配律:第一,参数是泛型类型,第二,代入参数的是联合类型

特殊的never

never是所有类型的子类型

  // never是所有类型的子类型
  type A1 = never extends 'x' ? string : number; // string

  type P<T> = T extends 'x' ? string : number;
  type A2 = P<never> // never

上面的示例中,A2和A1的结果竟然不一样,看起来never并不是一个联合类型,所以直接代入条件类型的定义即可,获取的结果应该和A1一直才对啊?

实际上,这里还是条件分配类型在起作用。never被认为是空的联合类型,也就是说,没有联合项的联合类型,所以还是满足上面的分配律,然而因为没有联合项可以分配,所以P<T>的表达式其实根本就没有执行,所以A2的定义也就类似于永远没有返回的函数一样,是never类型的。

防止条件判断中的分配

type P<T> = [T] extends ['x'] ? string : number;
type A1 = P<'x' | 'y'> // number
type A2 = P<never> // string

在条件判断类型的定义中,将泛型参数使用[]括起来,即可阻断条件判断类型的分配,此时,传入参数T的类型将被当做一个整体,不再分配。

Exclude排除

在 ts 中,我们能够使用 Exclude<T,U> 这个工具,帮助我们把 T 类型当中属于 U 类型的部分去除后得到一个新的类型,ts 已经自己提供了,使用方式如下:

type myType = Exclude<'a' | 'b' | 'c', 'a'>// 'b' | 'c'

源码

// 源码定义
type Exclude<T, U> = T extends U ? never : T;

Extract提取

将第二个参数的联合项从第一个参数的联合项中提取出来,当然,第二个参数可以含有第一个参数没有的项。

type Extract<T, U> = T extends U ? T : never
type A = Extract<'key1' | 'key2', 'key1'> // 'key1'

Pick 用于对象

原理

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
type User = {
    firstName: string,
    lastName: string,
    age: number
}
type UserName = Pick<User, "firstName" | "lastName">

现在,使用我们的新类型,UserName ,我们可以定义一个仅由firstNamelastName 组成的变量。

let user:UserName = {
    firstName: "John",
    lastName: "Doe"
}

Omit过滤

源码

/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Omit是新增的一个辅助类型,它的作用主要是:以一个类型为基础支持剔除某些属性,然后返回一个新类型。

type Person = {
    name: string;
    age: string;
    location: string;
};

type PersonWithoutLocation = Omit<Person, 'location'>;// {name: string, age: string}

type QuantumPerson = {
    name: string;
    age: string;
};

Record

Record<K, V>K为定义key值类型,V为定义value类型。

使用

type keys = 'a' | 'b'
// type 或 interface 都一样
type values<T> = {
    name?: T,
    age?: T,
    gender?: string
}

// 自定义 value 类型
type T1 = Record<keys, values<number | string>>
let obj:T1 = {
    a: { name: 'h' },
    b: { age: 18 }
}

// 固定 value 值
type T2 = Record<keys, 111>
let obj1:T2 = {
    a: 111,
    b: 111
}

源码

type Record<K extends any, V> = {
    [P in k]: V
}

Partial

Partial将类型中所有选项变为可选。

export interface PartialContact= Partial<Contact>
PartialContact{
  name?: string;
}

Partial深拷贝功能

ts提供的Partial只能作用于浅层,因此下面这个方法实现深层的。

type DeepPartial<T> = T extends Object ? { [P in keyof T]?: DeepPartial1<T[P]> } : T;

源码

type Partial<T> = {
    [P in keyof T]?: T[P]
}

Required

Required将类型中所有选项变为必选。

export interface RequiredContact= Required<Contact>
RequiredContact{
  name: string; // 姓
}

源码

type Required<T> {
    // -?变为必选
    [P in keyof T]-?: T[P];
}

Readonly

Readonly变为只读不可修改.

type User = {
    name: string
    age?: number
}
type T1 = Readonly<User>
// 简单说 T1 和 T2 是一模一样的
type T2 = {
    readonly name: string
    readonly age?: number
}

源码

type Readonly<T> {
    rendonly [P in keyof T]: T[P];
}

把所有只读类型,全都变成非只读就只需要 -readonly 就行了

NonNullable联合类型

NonNullable<T>:作用是去掉 T 中的 nullundefinedT 为字面量/具体类型的联合类型,如果是对象类型是没有效果的。如下

type T1 = NonNullable<string | number | undefined>;
// type T1 = string | number

type T2 = NonNullable<string[] | null | undefined>;    
// type T2 = string[]

type T3 = {
    name: string
    age: undefined
}
type T4 = NonNullable<T3> // 对象是不行的
type NonNullable<T> = T & {}

ObjNonNullable对象版

type ExcludeNon<T> = { [P in keyof T as T[P] extends null | undefined ? never : P] : T[P]};

参考链接

  1. TS关键字extends用法总结
  2. TS泛型进阶