博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
TypeScript的索引类型与映射类型,以及常用工具泛型的实现
阅读量:4117 次
发布时间:2019-05-25

本文共 4853 字,大约阅读时间需要 16 分钟。

相信现在很多小伙伴都在使用 TypeScript(以下简称 TS),在 TS 中除了一些常用的基本类型外,还有一些稍微高级一点的类型,这些就是我本次文章要讲的内容:索引类型与映射类型,希望小伙伴们看过这篇文章后能对 TS 有更深一步的理解。

索引类型

下面我通过一个官方的例子来说明下什么是索引类型:

function pluck(o, names) {  return names.map((n) => o[n])}

这是个简单的函数,names 是一个数组,里面是 key 值,我们可以从“o”里面取出这些 key 值,理想情况下 names 里面的 key 应该都是“o”里面包含的,否则最终的结果里面就会有 undefined,这个函数返回的结果也应该是“o”中都包含的 value 值,那么我们如何才能做到这些类型约束呢,如果只用一些基础类型,很难达到满意的效果,下面使用索引类型改写下:

function pluck
(o: T, names: K[]): T[K][] { return names.map((n) => o[n])}interface Person { name: string age: number}let person: Person = { name: 'Jarid', age: 35}let strings: string[] = pluck(person, ['name']) // ok, string[]

改写后这个函数是一个泛型函数,泛型为 T 和 K,其中 K 有点特殊,K extends keyof T,是什么意思呢,其中 keyof 就是索引类型查询操作符,我们从字面意思理解,它就是 T 的 key,就是 T 上已知的公共属性名的联合,对于上面的代码,keyof Person就是'name'|'age',那么K extends keyof T就是K extends 'name'|'age',这样我们就获取到了 Person 上所有 key 组成的一个联合类型,然后参数o: T, names: K[],就很好理解了,names 就是 K 组成的一个数组。返回值中T[K][]我们需要拆开来看 T[K]和[],就是 T[K]组成的一个数组,那么 T[K]是什么类型呢,它就是索引访问操作符,类似于 js 中对象的取值操作,不过这里取的是类型,因为 K 是'name'|'age',所以 T[K]就是string|number,这些就是索引类型,其实也不难理解,下面再说下映射类型,它和索引类型结合起来可以做很多事情。

映射类型

映射类型也很容易理解,我们先看一个简单的例子

type Keys = 'option1' | 'option2'type Flags = { [K in Keys]: boolean }

这个就是一个简单的映射类型,其中的in可以理解为是我们平时用的for...in,就是去遍历 Keys,然后把 boolean 赋给每一个 key,上面的 Flags 得到的结果就是

type Flags = {  option1: boolean  option2: boolean}

很简单吧,那么这个东西有什么用处呢,请看下面的例子:

// Persontype Person {    name: string    age: number}

我们想把这个 Person 里面的属性都变成只读的,像这样:

// Readonly Persontype Person {    readonly name: string    readonly age: number}

如果我们有很多这样的类型,那么改起来会很麻烦,因为每次都要把这个类型重新写一遍。其实我们可以使用刚才的索引类型和映射类型来写一个泛型:

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

[P in keyof T]就是遍历 T 中的 key,T[P]就是当前的 key 的类型,其实[P in keyof T]: T[P]就是把 T 遍历了一遍,但是我们在属性前面加了个 readonly,这样我们调用这个泛型的时候,它就会把传入的类型的 key 遍历一遍,遍历的同时在前面加个 readonly,最终给我们返回一个新的类型。我们在调用的时候只需要这么用:

type Readonly
= { readonly [P in keyof T]: T[P]}type Person { name: string age: number}type ReadonlyPerson = Readonly

索引类型和映射类型除了能实现 Readonly,还能实现很多有意思的东西,我们平时在使用 TS 的时候,TS 已经内置了一些常用的辅助泛型,刚才的 Readonly 就是其一,另外还有很多,我从 TS 的类型定义文件里找了一些,这些泛型从简单到复杂的都有,但基本上都是用上面提到的两个类型实现的,下面我们一起来分析一下。

TS 常用的辅助泛型及其实现方式

首先来看第一个

/** * Make all properties in T optional */type Partial
= { [P in keyof T]?: T[P]}

相信这个泛型很多人都用过,就是把类型都变成可选的,和刚才的 Readonly 是类似的实现方式,只是这个是在后面加了个问号,这样一来属性就变成可选的了。

与之相对的还有一个 Required

/** * Make all properties in T required */type Required
= { [P in keyof T]-?: T[P]}

注意这个稍有点不同,它是-?,其实就是减去问号,这样就可以把问号去掉,从而变成必选的属性。再来看下一个。

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

如果你理解了最开始的那个 pluck 函数,这个就很好理解了,我们传入 T 和 K,其中 K 是 T 的 keys 组成的联合类型,再看返回值[P in K]: T[P],就是把 K 遍历了一遍,同时赋值上原类型,那么综合来看 Pick 就是帮我们提取出某些类型的,比如通过Pick<Person, 'name'>我们就可以得到{name: string},再来看下一个

/** * Exclude from T those types that are assignable to U */type Exclude
= T extends U ? never : T

这个泛型传入一个 T 和 U,然后它判断了 T 是否属于 U,属于的话返回 never 否则返回原类型 T,注意 never 在最终的类型中是不会存在的,所以它可以帮助我们消除某些属性,其实这个 Exclude 就是消除了T extends U的类型,比如我们使用Exclude<'a'|'b','b'|'c'>,最终会得到'a',与之相反的有:

/** * Extract from T those types that are assignable to U */type Extract
= T extends U ? T : never

这个正好相反,是从 T 中取出 U 中拥有的类型。

有了 Exclude,我们就可以和 Pick 结合来实现另外一个:

/** * construct a type with the properties of T except for those in type K. */type Omit
= Pick
>

这个泛型是先使用了Exclude<keyof T, K>,去除了 keyof T 中的 K,然后又使用 Pick 取出了这些类型,这样我们就可以从 T 中去除 K 里面包含的 keys 了,达到和 Pick 相反的效果。

我们再来看另一个稍微复杂一点的

type NonNullObject
= Pick< O, { [K in keyof O]: O[K] extends null | undefined ? never : K }[keyof O]>

这个不是 TS 内置的类型,但也是一个很有用的类型,我们来一点一点分析。首先这个泛型使用了 Pick,我们知道 Pick 就是取出一些属性,我们先看传给 Pick 的第二个参数

{  [K in keyof O]: O[K] extends null | undefined ? never : K}[keyof O]

它遍历了 O 的 keys,然后进行了一个判断,如果是extends null | undefined则返回 never,否则返回 K,K 就是 O 中的 key 值,注意这里和之前的一些泛型有些不一样,之前的都是O[K],而这里的属性的值还是 K,最终我们得到的是类似K:K这样的东西,比如{name: string, age: null}这个,经过上面的转化会变成{name:'name', age:never},可能有些小伙伴还不清楚为什么要这样转换,我们接着往下分析,经过这个转换之后,又进行了一个操作[keyof O],对于 Person,keyof O 就是'name'|'age',那么这里就就是{name:'name', age:never}['name'|'age'],这样就很清晰了,其实就是一个取值操作,这样我们就可以得到'name'|never,还记得 never 的特性吗,它可以帮我们消除一些类型,那么最终的就是'name',这也是为什么我们写成类似 K:K 这样,就是要把 null|undefined 对应的 key 转换成 never,然后再通过 keyof 把他们全都取出来,别忘了最外面还有一个 Pick,这样我们就从原始类型中去除了 null|undefined。

另外还有一个比较有用的是 ReturnType

/** * Obtain the return type of a function type */type ReturnType
any> = T extends ( ...args: any) => infer R ? R : any

它可以帮我们取到函数返回值的类型,这个 ReturnType 接收的一个参数是函数,然后进行了一个判断T extends (...args: any) => infer R,就是判断是否是函数,这里有个东西是 infer,通过这个操作符我们可以获取 R 的引用,就是函数的返回值,最终再把 R 返回出去,就获得了函数 T 的返回值。

其实除了我分析的这些泛型,TS 还内置了其他的很多泛型,比如还有获取函数的参数的,获取构造函数类型的,总的来说各种泛型基本上都可以用索引类型和映射类型实现。

希望大家看过这篇文章后能多多使用这两种类型,在自己的项目里也能开发一些常用的辅助泛型,来提升工作效率。

本文完~

转载地址:http://jcbpi.baihongyu.com/

你可能感兴趣的文章
Valid Palindrome 简单的回文判断
查看>>
Pascal's Triangle -- 生成杨辉三角
查看>>
Pascal's Triangle II 生成杨辉三角中的某行
查看>>
Minimum Depth of Binary Tree -- 二叉树的最小深度 DFS 加剪枝
查看>>
Climbing Stairs 爬楼梯方法 动态规划
查看>>
Merge Two Sorted Lists 合并两个有序链表
查看>>
pow(x,n) 为什么错这么多次
查看>>
Jump Game 动态规划
查看>>
Binary Tree Maximum Path Sum 自底向上求解(重重重重)
查看>>
Subsets 深搜
查看>>
Subsets II
查看>>
Edit Distance 字符串距离(重重)
查看>>
Gray Code 格雷码
查看>>
对话周鸿袆:从程序员创业谈起
查看>>
web.py 0.3 新手指南 - 如何用Gmail发送邮件
查看>>
web.py 0.3 新手指南 - RESTful doctesting using app.request
查看>>
web.py 0.3 新手指南 - 使用db.query进行高级数据库查询
查看>>
web.py 0.3 新手指南 - 多数据库使用
查看>>
一步步开发 Spring MVC 应用
查看>>
python: extend (扩展) 与 append (追加) 的差别
查看>>