Typescript - - 高级用法

时间:2022-12-17 15:51:57

Typescript - - 高级用法

extends

extends 关键字在TS中的两种用法,即接口继承和条件判断。

接口继承

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

T1和T2两个接口,分别定义了name属性和sex属性,T3则使用extends使用多重继承的方式,继承了T1和T2,同时定义了自己的属性age,此时T3除了自己的属性外,还同时拥有了来自T1和T2的属性。

条件判断

  // 示例1
  interface Animal {
    eat(): void
  }
  interface Dog extends Animal {
    bite(): void
  }
  // A的类型为string
  type A = Dog extends Animal ? string : number
  const a: A = 'this is string'

Extends 用来条件判断的语法和 JS 的三元表达是很相似,如果问号前面的判断为真,则将第一个类型 string 赋值给 A,否则将第二个类型 number 赋值给 A。

extends判断条件真假的逻辑是什么?
如果 extends 前面的类型能够赋值给 extends 后面的类型,那么表达式判断为真,否则为假

泛型使用

TypeScript: Documentation - Conditional Types

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

!!!注意!!! :
When conditional types act on a generic type, they become *distributive* when given a union type
对于使用extends关键字的条件类型(即上面的三元表达式类型),如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,则使用分配律计算最终的结果。分配律是指,将联合类型的联合项拆成单项,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果。 所以上面的 A3 是 string | number
满足两个要点即可适用分配律:第一,参数是泛型类型,第二,代入参数的是联合类型
特别要注意的就是 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的类型将被当做一个整体,不再分配。

keyof

keyofObject.keys 略有相似,只不过 keyof 取interface 的键。

interface Point {
  x: number;
  y: number;
}
// 相当于:
// type keys = "x" | "y"
type keys = keyof Point;

实现一个函数,这个函数获取到一个对象的 value,参数是这个对象和 key

const object = {
	a: 1,
	b: 'zhangsan'
}
// 这样实现的弊端,1、 key 没有限定,我可以取 a、b 之外的了,2、返回的值没有限定,直接用 any 了
function get(obj: object, key: string): any {
	return obj[key]
}

// 改造
function get<T extends object, U extends keyof T>(obj: T, key: U): T[U] {
	return obj[key]
}
interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number.  这个的解释可以参考下面的链接

TypeScript: Documentation - TypeScript 2.9

Partial & Required & Pick

Typescript 已实现的,可以通过下面的去类比学习便于理解

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

type Required<T> = {
  [P in keyof T]-?: T[P];
};

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

interface User {
  id: number;
  age: number;
  name: string;
};

// 相当于: type PartialUser = { id?: number; age?: number; name?: string; }
type PartialUser = Partial<User>

// 相当于: type PickUser = { id: number; age: number; }
type PickUser = Pick<User, 'id' | 'age'>

never & Exclude & Extract & Omit

type Exclude<T, U> = T extends U ? never : T;

// 相当于: type A = 'a'
type A = Exclude<'x' | 'a', 'x'>
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>

// 与 Exclude 实现刚好相反,Exclude 取差集,而 Extract 取交集
type Extract<T, U> = T extends U ? T : never;

// 相当于: type A = 'x'
type A = Exclude<'x' | 'a', 'x'>

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
/* Omit 讲解
1、 keyof T 获取 T 的 key
2、 Exclude<keyof T, K> , K 就是我们要忽略的键,Exclude 找到 两个的差集
3、 Pick<T, 差集> 获取到的就是我们忽略了 K 后面留下来的
*/

interface User {
  id: number;
  age: number;
  name: string;
};

// 相当于: type PickUser = { age: number; name: string; }
type OmitUser = Omit<User, "id">

typeof

typeof 代表取某个值的 type

const a: number = 3
// 相当于: const b: number = 4
const b: typeof a = 4