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

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

在学习ts源码的时候,发现很多泛型还是看不懂,于是想写一篇文章,总结一下常用的泛型。

基础必备知识

联合类型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 提取字段

c83f8cdec4fb53ac8ca1b78bc1e2e743.png
// 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 反向获取

970cc786c6d8b50ff37d76119ff6ddef.png
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">;

案例二:两个接口的操作

我们把一个接口当作一个集合,那么两个集合的操作主要有:并集,交集,差集。

交集

b11257bb76092df9daef7e4d625b493d.png
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。如上图。

差集

a1576b571583c73da137d547fae47eaf.png
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。如上图。

并集

0365638340180018aedd603544b97fff.png

并集的定义:对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。通过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 泛型,解决了谁覆盖谁的问题。

b651b247ffc9763a13a85bfb4d38c10b.png
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>;

Node 社群

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

1399e0e98dd0877dce37519e9d46215c.png

   “分享、点赞、在看” 支持一波👍

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
泛型TypeScript 的一种特性,它允许在定义函数、类、接口等时使用类型参数,在使用时再确定具体的类型。这样可以增强代码的灵活性和重用性。 使用泛型的语法是在定义函数、类、接口等时在参数或返回值的类型前使用尖括号 `<>` 来定义类型参数。例如: ```typescript function identity<T>(arg: T): T { return arg; } ``` 上述代码,`<T>` 定义了一个类型参数 `T`,`arg` 参数的类型为 `T`,返回值的类型也为 `T`。 使用时,需要指定具体的类型。例如: ```typescript let output = identity<string>("hello world"); ``` 在这个例子,我们将 `identity` 函数的类型参数 `T` 指定为 `string`,这样 `arg` 参数和返回值的类型都会是 `string`。 当然,TypeScript 也支持类型推断,可以根据传入的参数自动推断类型参数。例如: ```typescript let output = identity("hello world"); ``` 因为传入的参数是 `string` 类型,TypeScript 会自动推断出类型参数 `T` 为 `string`,所以不需要显式指定类型参数。 除了函数,泛型还可以应用于类和接口的定义。例如: ```typescript interface List<T> { add(item: T): void; get(index: number): T; } class ArrayList<T> implements List<T> { private items: T[] = []; add(item: T) { this.items.push(item); } get(index: number): T { return this.items[index]; } } ``` 上述代码,`List` 接口和 `ArrayList` 类都使用了类型参数 `T`,`ArrayList` 类实现了 `List` 接口,并且在实现接口明确了类型参数的类型。这样,当使用 `ArrayList` 类时,也需要指定类型参数的具体类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值