TypeScript 学习笔记(四)--- 泛型(Generics)

(注明:目前泛型理解不够清晰,先照葫芦画瓢,整理一个粗略版本,后续会再次补充)

一、泛型的概念

​ 在TS中,泛型(Generics)是一种创建可复用代码的方式,类似于代码组件的概念。具体来说,就是在定义接口、函数或类的时候,不预先指定参数、返回值的类型,而是在使用时,根据具体情况来指定相应的类型。

​ 举个例子,定义一个函数 identity ,该函数有一个参数,函数内部就是实现了将参数原样返回,那么代码如下:

const identity = (arg) => arg;

​ 然后我们给代码加上类型声明,并使函数的入参和返回值都应该可以是任意类型:

type idBoolean = (arg: boolean) => boolean;
type idNumber = (arg: number) => number;
type idString = (arg: string) => string;
...

​ 上面的实现办法就是,TS有多少种类型就,写多少行代码,对应不同的类型签名,但这样会使代码冗余,难以维护。还有一种办法是使用any类型,不过这样会丧失类型检查功能,得不偿失:

const identity = (arg: any) => any;

// 哪怕返回值并没有这个方法,但依旧不会报错
identity("string").length; // ok
identity("string").toFixed(2); // ok
identity(null).toString(); // ok

​ 如果我们想要实现:根据传递的值得类型进行推导,并根据推导出来的类型,进行类型检查,比如我传了一个string,但是返回值使用量number的方法,那么就应该报错,这种效果,就需要借助泛型来实现:

function identity<T>(arg: T): T { 
  return arg;
}

​ 上面代码中的T,表示Type,是一个抽象类型,只有在调用函数时,才会确认具体的类型,这样就能适用于不同类型的数据。调用函数时,先把类型传递给 <T>中的T,然后再链式传递给参数类型和返回值类型。

在这里插入图片描述

​ 在泛型的<>中,我们可以定义任何数量的类型变量:

function identity <T, U>(value: T, message: U) : T {  
  console.log(message);  
  return value;
}
// 调用时,使用 <> 定义好对应类型变量的类型 像传参一样 一一对应
console.log(identity<Number, string>(68, "Semlinker"));

​ 在调用函数时,也可以省略使用 <> 显式设定类型,编译器可以自动根据我们的参数类型来得知对应类型:

function identity <T, U>(value: T, message: U) : T { 
  console.log(message);  
  return value;
}
// 省略 <> 
console.log(identity(68, "Semlinker"));

二、泛型约束

​ 如果我们不对泛型的类型变量进行约束,那么其类型理论上是可以是任何类型,那这样只能使用所有类型共有的属性或方法,否则就会报错:

function trace<T>(arg: T): T { 
  console.log(arg.size); // 报错 Error: Property 'size doesn't exist on type 'T' 
  return arg;
}

​ 所以我们要对其进行约束,方式就是通过 extends 继承一个接口:

interface Sizeable {
  size: number;
}
function trace<T extends Sizeable>(arg: T): T {
  console.log(arg.size);  
  return arg;
}

三、泛型工具类

1、概念

​ 为了方便开发,TS内置了一些常用的工具类型,比如 Partial、Required等。不过在学习这些内置工具类型之前,我们需要先去了解一些基础的TS的特性。

2、基础特性
① typeof

​ typeof 在 JS 中是用来判断值的类型,在 TS 中,typeof 不仅能判断基本数据类型,还可以判断接口类型:

interface Person { 
  name: string; 
  age: number;
}
const sem: Person = { name: "semlinker", age: 30 };
console.log(typeof sem) // Person
// 获取接口类型 并赋值给 类型变量
type Sem = typeof sem; // type Sem = Person
const lolo: Sem = { name: "lolo", age: 5 }

​ 以及获取对象的属性类型结构,作为一个类型赋值给类型变量:

const Message = {  
    name: "jimmy",   
    age: 18,   
    address: {   
      province: '四川',   
      city: '成都'     
    }
}
type message = typeof Message;
/*
 type message = {  
    name: string;  
    age: number;  
    address: {     
        province: string;      
        city: string;  
    };
}
*/

// 函数对象也可以
function toArray(x: number): Array<number> {
  return [x];
}
type Func = typeof toArray; // type Func = (x: number) => number[]
② keyof

​ keyof 操作符 是 TS 2.1 版本引入的,用来获取某种类型的所有键,其返回类型是一个联合类型:

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

// 获取接口类型的键
type K1 = keyof Person; // "name" | "age"
// 支持获取数组类型的键
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
// 支持普通数据类型的键
let K1: keyof boolean; // let K1: "valueOf"
let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...
let K3: keyof symbol; // let K1: "valueOf"
③ in

​ in 用来遍历枚举类型,比如联合类型:

type Keys = "a" | "b" | "c"

type Obj =  { 
  [p in Keys]: any
} // -> { a: any, b: any, c: any }
④ infer(不清晰)

​ 在条件类型语句中,可以用 infer 声明一个类型变量并使用:

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

​ 以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。

⑤ extends

​ extends 主要用于给泛型添加约束:

interface Lengthwise { 
  length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T { 
  console.log(arg.length); 
  return arg;
}
⑥ 映射类型

​ 根据已经存在的类型创建出新的类型,那被创建的新类型,我们就称为映射类型:

// 已存在的接口类型
interface TestInterface{  
    name:string,   
    age:number
}
// 处理已存在类型 并生成新类型的方法
type OptionalTestInterface<T> = { 
  [p in keyof T]+?:T[p]
}
// 获取新类型
type newTestInterface = OptionalTestInterface<TestInterface>
3、内置工具类型
① Partial

Partial<T> 结合keyof 和 in ,将某类型的所有属性变成可选:

// 工具类型 Partial 内部原理
type Partial<T> = { 
  [P in keyof T]?: T[P];
};
// 已存在的接口类型
interface UserInfo {  
    id: string;  
    name: string;
}
// 使用Partial
type NewUserInfo = Partial<UserInfo>;
const xiaoming: NewUserInfo = {  
    name: 'xiaoming'
}

​ 但 Partial 也有它的局限性:只支持对第一层属性进行处理,如果要处理的类型中有嵌套属性,则第二层往下就不会再处理。但如果想要对嵌套属性也进行处理,可以自己实现:

type DeepPartial<T> = {   
     // 如果是 object,则递归类型  
    [U in keyof T]?: T[U] extends object    
      ? DeepPartial<T[U]>   
      : T[U]
};

  type PartialedWindow = DeepPartial<T>; // 现在T上所有属性都变成了可选啦
② Required

​ Required将某类型的所有属性变成必选:

// Required 内部原理
type Required<T> = {    
    [P in keyof T]-?: T[P] // 其中 -? 是代表移除 ? 这个 modifier 的标识 
};
③ Readonly

Readonly<T> 的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值:

//  Readonly 内部原理
type Readonly<T> = { 
 readonly [P in keyof T]: T[P];
};
// 使用
interface Todo {
 title: string;
}
const todo: Readonly<Todo> = { 
 title: "Delete inactive users"
};
todo.title = "Hello"; // Error: cannot reassign a readonly property
④ Pick

​ Pick 从某个类型中挑出部分属性出来:

// 原理
type Pick<T, K extends keyof T> = {  
    [P in K]: T[P];
};
// 使用
interface Todo { 
  title: string; 
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;
  
const todo: TodoPreview = { 
  title: "Clean room", 
  completed: false,
};
⑤ ReturnType

​ ReturnType 用来得到一个函数的返回值类型:

// 原理
type ReturnType<T extends (...args: any[]) => any> = T extends ( 
  ...args: any[]
) => infer R   // infer在这里用于提取函数类型的返回值类型
  ? R 
  : any;
// 使用
type Func = (value: number) => string;
// ReturnType获取到 Func 的返回值类型为 string,所以,foo 也就只能被赋值为字符串了。
const foo: ReturnType<Func> = "1";
⑥ Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉,简单来说就是取不同的部分:

// 原理
type Exclude<T, U> = T extends U ? never : T;
// 使用
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
⑦ Extract

Extract<T, U> 的作用是从 T 中提取出 U,简单来说就是取相同的部分:

// 原理
type Extract<T, U> = T extends U ? T : never;
// 使用
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void
⑧ NonNullable

NonNullable<T> 的作用是用来过滤类型中的 nullundefined 类型:

// 原理
type NonNullable<T> = T extendsnull | undefined ? never : T;
// 使用
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
⑨ Record、Omit、Parameters等
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的小朱同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值