泛型入门
泛型简单来说可以理解成把类型当变量传到类型定义里,就如同参数传到函数一样,例如:
使用 <>
包裹 Type
就能把类型传入泛型函数,上面函数的效果是:接受类型为 Type
的参数,返回类型为 Type
的结果。
既然 Type
是个参数,那名字自然也是很自由的,我们常常会使用 T
当参数的名称,所以可以这么写:
自我推断
在使用泛型函数时可以不明确指出 T
的类型(而且我们通常都会这么做),此时 TS 将自动推断 T
的类型:
还是上面的例子,如果不显式指定类型 <string>
,TS 就直接推断 "myString"
的类型为 T
,所以这样函数返回的也是字符串。
画个圈
默认状态下泛型可以是任何类型,这样可读性就很低了,而且在对“施加了泛型”的类型进行操作或调用起方法时,因为是任意类型,必然不能通过检查,为了解决这个问题,可以通过 extends
给泛型画个圈,框定它的类型。
上面的代码通过 <T extends Lengthwise>
表明这个 T
必须是一个有 length
属性的类型,任何有 length
属性的类型都满足这个泛型的需求,例如,它也可以是一个数组。
绑定能力
据官网所说泛型用于复用类型,相信经过上面的简单介绍也会觉得这确实十分有效。但是泛型除了用于类型复用,还有什么其他运用呢?
我的答案是类型的联动,T 可以对同一个类型定义内运用到的其他泛型进行绑定。
再看一眼这个例子,其实他就是把输入的类型和输出的类型进行了绑定:
下面看一个“类型绑定”玩法更显眼的例子。
映射类型
假设有一个对象,它的 key 是 a、b、c,值是不同函数,现在我们需要得到一个 key 和对应函数参数的对象的类型,该如何实现呢?
如果这么写就坏了,Answer
只是一个 key 为 MyKey
,value 为 Parameters<MyMap[MyKey]>
的对象,但是这两者间丢失了 myMap
定义的关系,变成这样:
所以这时候其实就要用到泛型对类型的绑定能力啦!正确答案如下:
K
是类型 myMap
的 key,并且,Answer2
的值必须是 MyMap[K]
的参数。这样就绑定了 Key 和值的固定关系。
甚至在新版本还有这种花里胡哨的,你可以把属性类型再 as
一次:
输出结果如下:
P.S. as
其实是什么?官方文档称为 Type Assertions,用于把一个类型 as
为另一个类型,但是这两个类型,可以往小里 as
,也可以往大去 as
,但是必须有一方是另一方的子集。在 LazyPerson
的例子中因为说到底全是 string
,所以可以使用 as
。
实战
下面先放出题目,有兴趣可以先自己思考一下,如何完善下面 JS 函数的类型?(答案在下面,先别翻下去哦)
题目
首先我们有一个 myMap
,但不直接使用它,而是先通过 wrapper
把它包装一下,这样就可以实现在运行函数时先做某些前置操作了,那么问题是,wrappedMap
的类型怎么写呢?
答案
现在确实是已经做到了 WrappedMap
的类型是 wrapper 返回值的效果,但是,这句 (fn as any).apply(null, arg)
,是不是显得很突兀?
为什么还需要把 fn 置为 any?
因为对 TS 来说 a
、b
、c
根本没有和他的值的参数类型进行绑定,所以即使用了 T
进行限制也没有效果,这句话可能有点拗口,接着看下面的答案 2 可能会更清晰。
答案 2
去除 (fn as any)
的解法是,先另外造一个 map 把你需要关联的东西先映射一遍,就是上面的 MyMapArgs
,接着再用这个映射造出 MyMap
,这样 TS 才终于明白这两个东西是有关系的。
P.S. 更详细的信息可以参考 issues#30581 和 pull#47109