走进ts-工具泛型

时间:2021-1-18 作者:admin

写在前面

上一篇文章简单介绍了泛型的概念,以及函数泛型、类泛型、接口泛型的使用方式,还有多参数泛型和泛型约束的方法。本篇将介绍下ts官网提供的一些工具泛型,什么是工具泛型呢?简单来说就是可以把一个复杂的类型转变成我们想要的类型。以下代码举例

interface StudentInfos {
    name: string
    age: number
    tall: number
    weight: number
    favor: string[]
    sex: 'boy' | 'girl'
    country: string
    id: string
    mother: string
    father: string
}

这是一个学生信息的接口,但是有些业务只关心tall和weight,有一些业务又只关心mother和father,于是乎我们又增加了两个接口

interface StudentBodyInfos {
    tall: number
    weight: number
}

interface StudentParentsInfos {
   mother: string
   father: string
}

如果又有其他的业务既关心学生tall和weight,又关心mother和father,那么又会增加一些接口,如此一来,项目中管理各种各样重复的接口类型也是一笔不小的维护开销,如何做到灵活处理呢?那么活用工具泛型无疑可以大大省去这些开销,比如使用Pick

const studentBodyInfo:Pick<StudentInfos, 'tall' | 'weight'> = {
    tall: 165,
    weight: 120,
}
const studentParentsInfo:Pick<StudentInfos, 'mother' | 'father'> = {
    mother: 'mother',
    father: 'father',
}

这里的Pick也是工具泛型的一种,可以从一个复杂的类型中选择需要的属性返回一个新类型。相比起遇到细粒度的类型重复去新建interface,使用Pick维护简单,而且不需要管理诸多的Interface。

Pick实现

/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

这里要讲3个点,不然后面的工具泛型也看不懂

  • in 遍历联合类型
  • keyof指代遍历出类型的key 返回联合类型
type StudentInfoKeys = keyof StudentInfos;

// 等同以下写法
type StudentInfoKeys = "name" | "age" | "tall" | "weight" | "favor" | "sex" | "country" | "id" | "mother" | "father"
  • extends在ts用法很多,可以泛型约束,类/接口继承,笔者认为把这里理解泛型约束,要求我们传入的必须是StudentInfoKeys的子集。此处再补充一点extends也可以拿来做条件判断。
T extends U ? X : Y
// 如果T包含的类型 是U包含的类型的 '子集',那么取结果X,否则取结果Y

总结Pick的用处

  • 给定一个复杂的类/接口T
  • 约束传入的key的联合类型K属于T所有keys的联合类型的子集
  • 遍历K,并要求遍历中的P类型为T中P的类型

还有很多的工具类型,大体上都是通过in keyof extends这些进行设计,下面来看看都有哪些实用的工具泛型吧。

Partial

让一个类型所有的props可选,适用于一些对类型结构需求不明确,需要灵活处理的场景。

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

const someStudentsInfo: Partial<StudentInfos> = {
    tall: 165,
    weight: 120,
    mother: 'mother'
}

PickWithPartial

小插曲,我们想让Pick返回的类型也props可选,参照Partial得出我们新造出来的新工具泛型

type PickWithPartial<T, K extends keyof T> = {
    [P in K]?: T[P];
};
const someStudentBodyInfo:PickWithPartial<StudentInfos, 'tall' | 'weight'> = {
    tall: 165
}

Required

前有Partial,相反地想让所有的属性必选,怎么处理呢?这里要说下 – 的用法。

  • -表示逻辑取反 比如 -? 表示 非可选。

那么Required就很简单了

type Required<T> = {
    [P in keyof T]-?: T[P];
};
interface PartProps {
    a?: string
    b?: string
}
// 因为Required,所以要求a, b属性必选
const a:Required<PartProps> = {
   a: 'a',
   b: 'b'
}

Readonly

所有属性只读

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

Record

由联合类型的key当做新类型的key

type Record<K extends keyof any, T> = {
    [P in K]: T;
};
type RecordKeys = 'a' | 1;
const a : Record<RecordKeys, string> = {
    a: 'a',
    1: 'a'
}

// 更合理的场景
type StudentsName = '小明' | '小张' | '小王'
const studentsInfo : Record<StudentsName, StudentInfos> = {
    '小张':  {} as StudentInfos,
    '小明': {} as StudentInfos,
    '小王': {} as StudentInfos,
}

Exclude

从一个联合类型中排除掉属于另一个联合类型的子集

type Exclude<T, U> = T extends U ? never : T;

type name = Exclude<'小明' | '小张', '小张' | '小王'>

// 等同
type name = '小明'

Extract

跟Exclude相反,从从一个联合类型中取出属于另一个联合类型的子集

type Extract<T, U> = T extends U ? T : never;

type name = Exclude<'小明' | '小张', '小张' | '小王'>

// 等同
type name = '小张'

Omit

跟Pick相反,把选出的排除掉

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
  • Exclude<keyof T, K> 排除掉属于K的子集
  • Pick<T, Exclude<keyof T, K>> 选出被排除掉的剩下的props

NonNullable

排除null/undefined类型

type NonNullable<T> = T extends null | undefined ? never : T;

type studentName = NonNullable<string | null | undefined> // string

ReturnType

返回函数的返回结果类型

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type resType = ReturnType<(arg: { name: string }) => string>
// 等同于
type resType = string;

这里一个新字眼infer,作用是命名变量,这里可以理解成把返回结果类型通过Infer命名成R

自由构建

ts工具泛型非常多且实用,大多通过in keyof extends infer -这些关键字进行构筑,理解了这些才能自由自在的构筑自己的工具泛型。比如有个需求,通过调取函数获得班级学生的姓名,再通过姓名构建key的类型映射。

type StudentName = '小张' | '小明' | '小王';
type StudentRizeler<T extends () => StudentName, K> = T extends (...args: any) => StudentName ? Record<StudentName, K> : any;
const demo: StudentRizeler<() => StudentName, StudentInfos> = {
    '小张': {} as StudentInfos,
    '小明': {} as StudentInfos,
    '小王': {} as StudentInfos,
}

const demo2: StudentRizeler<() => StudentName, Pick<StudentInfos, 'age' | 'favor'>> = {
    '小张': {
        age: 25,
        favor: ['eat']
    },
    '小明': {
        age: 26,
        favor: ['sleep']
    },
    '小王': {
        age: 15,
        favor: ['play']
    },
}

总结

ts不仅提供的强大的类型系统,外加提供的开放能力也允许开发者自由的构建各种各样的类型。工具泛型在提高代码灵活性降低管理interface的同时,自定义的一些工具泛型也是一抹亮丽的风景啊~

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。