likes
comments
collection
share

TypeScript泛型以及常用的类型编程套路整理

作者站长头像
站长
· 阅读数 14

自我介绍

前言

现在TypeScript在项目中的运用非常多,TS能够焕发生命力的原因有一部分是因为它能提供完善的类型提示和类型检查,能够静态检查出很多错误,提高代码的健壮性。

因此定义好类型是在项目当中发挥TS作用的关键(不然就变成anyScript了),但通常在项目开发当中,仅仅通过简单的类型无法满足我们的需求,需要使用到结合泛型的类型编程。

在这里整理了一些平时常用到的类型编程以及实现的过程,持续更新。。。

另外有不恰当的地方欢迎大家指正!

前置知识:什么是泛型?以及泛型基础

前排提醒:如果了解的可以跳过本章节

引出

在typescript当中,通常会用确定的类型,比如自己声明的interfacetype或者基础的类型、交叉类型、联合类型等来定义

确定的类型固然很好,但其实会有一些需求,比如:

实现一个函数,输入是什么类型,就返回什么类型

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
}


TS Playground

虽然说能实现,但每次添加新的类型都需要去动态维护,这显然是不符合程序员习惯的。

这时可能有部分小可爱选择用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泛型以及常用的类型编程套路整理

简单理解就是: 类型也可以像变量一样传入,当你在使用的时候,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这个关键字就是做这个的。

比方说之前的函数 我只想让Tstringnumber ,那么可以这样写


function fn<T extends string | number,U>(foo:T,bar:U):void{

    console.log('传入的参数是:')

    console.log(foo,bar)

}
fn(false,99) // 使用

当传入为约束范围外的类型的时候,就会报错 TypeScript泛型以及常用的类型编程套路整理

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泛型以及常用的类型编程套路整理

工具类型

工具类型可以是TypeScript的官方封装集成的,方便开发者去用的,我们平时开发过程当中其实也可以自己封装属于自己的工具类型。

Partial

将属性类型转成可选

type Partial<T> = { [P in keyof T]?: T[P]; }

TypeScript泛型以及常用的类型编程套路整理

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

获取函数的返回值类型 TypeScript泛型以及常用的类型编程套路整理

Record

将K(可以是联合类型或者基础类型),的每一个作为属性值,类型设置为T TypeScript泛型以及常用的类型编程套路整理

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..]取的联合类型,配合泛型约束即可

TypeScript泛型以及常用的类型编程套路整理

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
评论
请登录