天天看點

TypeScript 中的泛型你真搞懂了嗎?

基礎必備知識

聯合類型vs交叉類型

// 聯合類型
interface Bird {
  name: string;
  fly(): void;
}
interface Person {
  name: string;
  talk(): void;
}
type BirdPerson = Bird | Person;
let p: BirdPerson = { name: "zfeng", fly() {} }; 
let p1: BirdPerson = { name: "zfeng", talk() {} };      

聯合類型使用 “|”表示或的關系, 滿足其中的一個情況即可。

interface Bird {
  name: string;
  fly(): void;
}
interface Person {
  name: string;
  talk(): void;
}
type BirdPerson = Bird & Person;
let p: BirdPerson = { name: "zhufeng", fly() {}, talk() {} };      

交叉類型使用“&”,表示與的關系,需要滿足所有的情況。

内置條件類型

type Extract<T, U> = T extends U ? T : never;
type Exclude<T, U> = T extends U ? never : T;
type NonNullable<T> = T extends null | undefined ? never : T;

type N = NonNullable<string | number | null | undefined>;// 删除null和undifined;
type E = Exclude<string | number, string>; // 排除關系 輸出 string;
type I = Extract<string | number, string>; // 包含關系 輸出 number;      

函數的類型推斷

擷取函數傳回值的類型

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

function getUserInfo(name: string, age: number) {
  return { name, age };
}
type UserInfo = ReturnType<typeof getUserInfo>;

const userA: UserInfo = {
  name: "zhufeng",
  age: 10,
};      

擷取函數參數的類型

type Parameters<T> = T extends (...args: infer R) => any ? R : any;
function getUserInfo(name: string, age: number) {
  return { name, age };
}
type T1 = Parameters<typeof getUserInfo>;  // [name: string, age: number]      

泛型進階

很多人對于泛型的了解還停留在基礎的層面,我講站在集合的視角去了解一下什麼叫泛型。

案例一:字段的提取

給定一個接口 Persion,  裡面有name,age,visiable,三個字段,現在的要求是:得到一個新的接口,裡面隻有name,age。一般人常見的思路:

interface Person {
  name: string;
  age: number;
  visiable: boolean;
}

interface Person1 {
  name: string;
  age: number;
}      

我們從寫一個接口,就可以達到要求。但是這樣子的寫法,顯得十分備援。其實ts提供了方法,讓我們可以實作,讓我們一起看一下的例子。

方式一:Pick 提取字段

TypeScript 中的泛型你真搞懂了嗎?
// pick 的原理
// type Pick<T, K extends keyof T> = { [P in K]: T[P] };
interface Person {
  name: string;
  age: number;
  visiable: boolean;
}
type Person1 = Pick<Person, 'name'|'age'> ;      

Person1 就包含 name,age 字段。

方式二:Omit 反向擷取

TypeScript 中的泛型你真搞懂了嗎?
interface Person {
  name: string;
  age: number;
  visiable: boolean;
}
type Exclude<T, U> = T extends U ? never : T;
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Person2 = Omit<Person, "age">;      

案例二:兩個接口的操作

我們把一個接口當作一個集合,那麼兩個集合的操作主要有:并集,交集,差集。

交集

TypeScript 中的泛型你真搞懂了嗎?
type Extract<T, U> = T extends U ? T : never;
type Intersection<T extends object, U extends object> = Pick<
  T,
  Extract<keyof T, keyof U> & Extract<keyof U, keyof T>
>;

type C1 = { name: string; age: number; visible: boolean };
type C2 = { name: string; age: number; sex: number };

type C3 = Intersection<C1, C2>;      

交集的定義:對于給定的兩個集合,傳回一個包含兩個集合中共有元素的新集合。通過Intersection實作交集,可以獲得一個新接口,C3隻包含 name.age。如上圖。

差集

TypeScript 中的泛型你真搞懂了嗎?
type Exclude<T, U> = T extends U ? never : T;
type Diff<T extends object, U extends object> = Pick<
  T,
  Exclude<keyof T, keyof U>
>;

type C1 = { name: string; age: number; visible: boolean };
type C2 = { name: string; age: number; sex: number };

type C11 = Diff<C1, C2>;      

差集的定義:對于給定的兩個集合,傳回一個包含所有存在于第一個集合且不存在于第二個集合的元素的新集合。通過Diff實作差集,可以獲得一個新接口,接口隻有visiable。如上圖。

并集

TypeScript 中的泛型你真搞懂了嗎?

并集的定義:對于給定的兩個集合,傳回一個包含兩個集合中所有元素的新集合。通過Merge實作并集,可以獲得一個新接口,接口包含C1,C2 的所有屬性。如上圖。

//Compute的作用是将交叉類型合并
type Compute<A extends any> = A extends Function ? A : { [K in keyof A]: A[K] };
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
type Merge<O1 extends object, O2 extends object> = Compute< O1 & Omit<O2, keyof O1>>;
type C1C2 = Merge<C1, C2>;      

特殊的情況:Overwrite(覆寫)

type C1 = { name: string; age: number; visible: boolean };
type C2 = { name: string; age: string; sex: number };      

C1,C2做merge, C1中有age,類型為number,C2中有age,類型為string,那麼合并之後,age是string,還是number類型呢?

Overwrite 泛型,解決了誰覆寫誰的問題。

TypeScript 中的泛型你真搞懂了嗎?
type C1 = { name: string; age: number; visible: boolean };
type C2 = { name: string; age: string; sex: number };

type Overwrite<
  T extends object,
  U extends object,
  I = Diff<T, U> & Intersection<U, T>
> = Pick<I, keyof I>;
  
type overwrite = Overwrite<C1, C2>;