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>
的作用是用来过滤类型中的 null
及 undefined
类型:
// 原理
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等
TypeScript 学习笔记(四)--- 泛型(Generics)相关推荐
- Typescript 学习笔记七:泛型
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- Typescript 学习笔记一:介绍、安装、编译
前言 整理了一下 Typescript 的学习笔记,方便后期遗忘某个知识点的时候,快速回忆. 为了避免凌乱,用 gitbook 结合 marketdown 整理的. github地址是:ts-gitb ...
- C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻
前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...
- IOS学习笔记(四)之UITextField和UITextView控件学习
IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...
- RabbitMQ学习笔记四:RabbitMQ命令(附疑难问题解决)
RabbitMQ学习笔记四:RabbitMQ命令(附疑难问题解决) 参考文章: (1)RabbitMQ学习笔记四:RabbitMQ命令(附疑难问题解决) (2)https://www.cnblogs. ...
- JSP学习笔记(四十九):抛弃POI,使用iText生成Word文档
POI操作excel的确很优秀,操作word的功能却不敢令人恭维.我们可以利用iText生成rtf文档,扩展名使用doc即可. 使用iText生成rtf,除了iText的包外,还需要额外的一个支持rt ...
- Ethernet/IP 学习笔记四
Ethernet/IP 学习笔记四 EtherNet/IP Quick Start for Vendors Handbook (PUB213R0): https://www.odva.org/Port ...
- TypeScript学习笔记3:运算符
TS 和 JS 相对比的优势 TypeScript的安装步骤.运行问题及代码的简单运行 TypeScript学习笔记1:变量赋值及书写方式 TypeScript学习笔记2:数据类型 文章目录 运算符 ...
- TypeScript学习笔记2:数据类型
TS 和 JS 相对比的优势 TypeScript的安装步骤.运行问题及代码的简单运行 TypeScript学习笔记1:变量赋值及书写方式 TypeScript学习笔记2:数据类型 文章目录 数据类型 ...
- TypeScript学习笔记1:变量赋值及书写方式
TS 和 JS 相对比的优势 TypeScript的安装步骤.运行问题及代码的简单运行 TypeScript学习笔记1:变量赋值及书写方式 TypeScript学习笔记2:数据类型 文章目录 变量赋值 ...
最新文章
- 用composer安装laravel-bjyblog
- Modelsim下进行功能仿真没问题,可是在ISE综合报错,如何解决?
- 【Netty】使用 Netty 开发 HTTP 服务器 ( HTTP 请求过滤 )
- python 分类变量回归_极简Python带你探索分类与回归的奥秘
- WPF之鼠标滑动切换图片
- /usr/bin/python^M: bad interpreter: No such file or directory
- 腾讯朋友、钉钉等被微信违规公示点名;谷歌更新安卓修复数百万台芯片漏洞;微软终止支持.NET Core 3.0 | 极客头条...
- 项目中js文件修改后浏览器不能及时更新的解决办法
- 数据结构之算法与线性表
- lc滤波器是利用电感的感抗_电感器在电路中的应用特性
- ROI和widthStep
- python数据结构之动态规划
- VScode环境变量配置
- 管道读写报错121:信号灯超时时间已到
- Gitee+PicGo+Typora图床搭建丨出现 Error: You must be logged in to use 的解决方法
- 入门pandas—数据透视与交叉表
- 02C++对C的增强
- 信息年龄、新鲜度、数据寿命、边缘计算等读书报告
- 操作系统之哲学原理 第2版
- 视频号引流有哪些方法?学会这几种方法快速吸粉