TypeScript泛型以及常用的类型编程套路整理
自我介绍
前言
现在TypeScript在项目中的运用非常多,TS能够焕发生命力的原因有一部分是因为它能提供完善的类型提示和类型检查,能够静态检查出很多错误,提高代码的健壮性。
因此定义好类型是在项目当中发挥TS作用的关键(不然就变成anyScript
了),但通常在项目开发当中,仅仅通过简单的类型无法满足我们的需求,需要使用到结合泛型的类型编程。
在这里整理了一些平时常用到的类型编程以及实现的过程,持续更新。。。
另外有不恰当的地方欢迎大家指正!
前置知识:什么是泛型?以及泛型基础
前排提醒:如果了解的可以跳过本章节
引出
在typescript当中,通常会用确定的类型
,比如自己声明的interface
、type
或者基础的类型、交叉类型、联合类型等来定义
确定的类型固然很好,但其实会有一些需求,比如:
实现一个函数,输入是什么类型,就返回什么类型
const fn =(foo:number):number=>{}
const fn =(foo:boolean):boolean=>{}
const fn =(foo:string):string=>{}
//... 继续写?
或者说利用重载?
function fu(foo:number):number
function fu(foo:boolean):boolean
function fu(foo:string):string
function fu(foo:string | number | boolean):string | number | boolean{
return foo
}
虽然说能实现,但每次添加新的类型
都需要去动态维护,这显然是不符合程序员习惯的。
这时可能有部分小可爱选择用any了,直接any梭哈!
const fn =(foo:any):any=>{}
用any就没有起到类型约束了,通常这也是ts变成anyscript的高发场景。
因此这种情况下,泛型就适用了
const fn = <T extends unknown>(foo:T):T=>{}
// 或者
function fn<T>(foo:T):T{
//...
}
ps:箭头函数
下需要约束一下T的类型告诉编译器,不然会以为你是jsx
简单理解就是: 类型也可以像变量一样传入,当你在使用的时候,typescript强大的类型系统会做自动的类型推导,推导出当前情况下T
等泛型参数所代表的类型。
举个例子: 比如定于一个这样的函数
function fn<T,U>(foo:T,bar:U):void{
console.log('传入的参数是:')
console.log(foo,bar)
}
fn('JetTsang',99) // 使用
当调用这个函数时,编译器会自动推导得出T
的类型是string
,U
的类型是number
。
操作符
跟泛型相关的操作符有以下
extends
既然泛型参数比较自由,那要是想让它代表的类型是我们想要的一些呢?
这就是泛型约束
的概念了,extends
这个关键字就是做这个的。
比方说之前的函数 我只想让T
为string
和 number
,那么可以这样写
function fn<T extends string | number,U>(foo:T,bar:U):void{
console.log('传入的参数是:')
console.log(foo,bar)
}
fn(false,99) // 使用
当传入为约束范围外的类型
的时候,就会报错
typeof
这个有点类似于JS里头相同的关键字,不过其实有差别,JS里头能取到基本数据类型和function。对object和array等引用类型是的‘object’。
但在typescript当中,它通常用来获取声明的变量、属性或函数的类型,使用在类型声明当中
官方文档:typeof
基础用法
const arr:number[] = [1]
type Arr = typeof arr
const arr2:Arr = {name:"jettsang"} ❌
//Type '{ name: string; }' is not assignable to type 'number[]'.
可以获取到函数的类型
type Predicate = (x: unknown) => boolean;
const fu:Predicate = (arr:unknown)=>{
if(Array.isArray(arr)){
return (arr as any[]).length > 0
}else{
return false
}
}
type Fn = typeof fu; //这里相当于 type Fn = (x: unknown) => boolean
结合工具类型可以获取到函数返回的类型
type U = ReturnType< typeof fu> //这里相当于 type K = boolean
// 因为前面fu: Predicate,所以也可以写成这样
type K = ReturnType<Predicate>; //这里相当于 type K = boolean
注意:不能直接使用函数运行后的结果
用来做typeof
type E = typeof fu([1,2,3]) // ';' expected.(1005)
keyof
keyof
可以获取某种类型的所有键,其返回类型是联合类型。
官方文档:keyof
interface People {
name:string,
age: number
}
type Keys = keyof People // type Keys = 'name' | 'age'
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish; // type A = number
type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // type M = string | number
简单说一下为什么M是string | number
的联合类型,因为JavaScript object的 keys,通常都被转成string。比如你定义一个object:{'99':"JetTsang"}
,那么在你取值时,object[99]
,和object['99']
都能奏效。
in
in
其实就类似于Javascript当中的for let in
,用来遍历联合类型
type Keys = 'name' | 'address'
type Prople = {
[key in Keys] : string
}
工具类型
工具类型可以是TypeScript的官方封装集成的,方便开发者去用的,我们平时开发过程当中其实也可以自己封装属于自己的工具类型。
Partial
将属性类型转成可选
type Partial<T> = { [P in keyof T]?: T[P]; }
Pick
从T当中取得U当中的属性 ,U是联合类型或者字面量类型或基础类型
type Pick<T, U extends keyof T> = { [P in U]: T[P]; }
Exclude
从U里排除T,U一般是对象结构类型
type Exclude<T, U> = T extends U ? never : T;
Omit
从T里排除一些属性K,一般在对象结构类型当中使用
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 意思: 用Pick 从 T 当中 获取到 排除了 T的所有Keys 中的 所有K 之后的对象结构类型
Extract
从T里提取出U,通常是联合类型
type Extract<T, U> = T extends U ? T : never
type People = 'name' | 'address'
type Name = Extract<People,'address'>
ReturnType
获取函数的返回值类型
Record
将K(可以是联合类型或者基础类型),的每一个作为属性值,类型设置为T
Readonly
顾名思义,将属性变为只读
type Readonly<T> = { readonly [P in keyof T]: T[P]; }
Readonly
顾名思义,将属性变为必须(也就是去除可选,跟Partial
相反)
type Required<T> = { [P in keyof T]-?: T[P]; }
-?
这里意思是:移除可选
NonNullable
过滤掉类型中的 null 和 undefined 。
type NonNullable<T> = T & {}
小结
利用到关键字和工具类对泛型进行约束,推导,运算等,从而生成目标类型,这就是类型编程
。如果把类型看成变量,其实就是函数编程嘛。
案例积累
需求1
请求过程当中,完善put函数的类型
type Apis = 'user' | 'categoryList' | 'menuList'
interface DataType {
id: number,
endTime?: string
// 一旦api确定,这里要取api为key值
// user?: string
// categoryList?: string
// menuList?: string
}
const put = (api: APis, data:DataType):Promise<AxiosResponse> =>{
// 。。。。
}
参数api
的类型要和data
里的第三个字段对应上,要给data上一个类型
可能有同学会想这样做
type dataType = {
id: number,
categoryList: string,
endTime?: string
} | {
id: number,
user: string,
endTime?: string
}| {
id: number,
menuList: string,
endTime?: string
}
没错,枚举出来联合类型确实可以解决
解决
但这样手动维护太麻烦了,可以利用泛型
type Apis = 'user' | 'list' | 'table'
interface BaseData {
id: number,
endTime?: string,
}
// 用泛型约束后,将Apis取出来作为key得到所有的类型,
// 再用{}[T]这样取到对应的值
type DataType<T extends Apis> = {
[key in T]: {
[k in key]: string
} & BaseData
}[T]
想到Record
这个泛型工具类,不由得能改进上面的逻辑
// 想到type Record<K extends string | number | symbol, T> = { [P in K]: T; }
// 可以把里面那一层改成
type DataType<T extends Apis> = {
[key in T]: Record<key,string> & BaseData
}[T]
// 再进一步把外面也改掉
type DataType<T extends Apis> = Record<T,Record<T,string> & BaseData>[T]
const put = <T extends Apis>(api:T ,data:DataType<T>):Promise<AxiosResponse> =>{
// 。。。。
}
需求2
有个table的排序类型,当其中1个字段不为false的时候,要约束其余字段为false 例子:
type TableFilterState = false | 'desc' | 'asc'
interface TableSortState {
'createTime': TableFilterState,
'updateTime': TableFilterState,
'name': TableFilterState,
'gender': TableFilterState,
}
解决
其实也是利用相同的思路,因为剩下的字段都为false,
先枚举出来,利用{key1:...}[key1..]
取的联合类型,配合泛型约束即可
type TableFilterState = false | 'desc' | 'asc'
type Columns = 'createTime' | 'updateTime' | 'name' | 'gender'
type TableSortState<T extends Columns> = {
[keys in T]: { [key in keys] : Exclude<TableFilterState,false> }
& { [otherKey in Exclude<Columns,keys>]: false }
}[T]
const tableSortState : TableSortState< Columns > = {
'createTime': 'desc',
'updateTime': false,
'name': false,
'gender': false,
}
需求n
不断更新中,同时也欢迎大家补充和指正👏👏👏
转载自:https://juejin.cn/post/7230737732733255741