TS中的泛型
泛型的引入增强了TS类型表达能力,使用泛型的类型就像使用参数的函数,可以将不变的部分固定下来,变的部分通过参数根据不同需要为参数赋不同值。
泛型的基础使用
泛型的主要作用于关联两个变量,甚至文档中说”如果一个泛型变量没有出现在两个地方,那么就需要再考虑一下引入泛型的必要性。“
问题1:当要求函数入参和出参类型保持一致,入参是number
类型返回值也要是number
类型,当是string
类型返回值也要是string
类型时:
type fn = (arg: any) => any
这样的表达可以满足要求,但是当入参是string
出参同样可以是number
,入参和出参之间需要一致的关系没有表达出来。
type fn = <T>(arg: T) => T
引入泛型后可以满足要求,因为泛型T
表达了入参和出参需要保持一致,都是类型T
。
泛型约束
在问题1的基础上进行优化,要求函数入参类型为number | string
且要求入参和出参保持一致,使用泛型实现如下:
type fn = <T extends number | string>(arg: T) => T
约束泛型只能是string | number
类型,入参其他类型编译会报错。
泛型约束传播
泛型没有约束时默认值为unknow
,一旦添加约束后泛型就就表现为约束类型,这种现象被称为泛型约束传播。
unknow
类型变量可以被赋值任意类型,但是无法对unknow
类型变阿玲进行任意操作。如果吧any
类型分为任意写和任意读,那么unknow
具有了any
的一半能力”任意写“。
type fn = <T extends string>(arg: T) => voidconst f: fn = (arg) => arg.toUpperCase()
以上例子类型T
被从unknow
约束成类型string
所以即使没有赋值类型,也可以在函数体内正对类型T
的变量调用toUpperCase()
方法。
为什么在上面例子中type fn
的返回值是void
而不是T
type fn = <T extends string>(arg: T) => T
const f: fn = (arg) => arg.toUpperCase()
因为泛型变量T
是string
类型子元素,T
可能是具体类型例如"Type"
字符串,而toUpperCase
返回值是string
类型,匹配不上所以报错,但是和阐述的泛型约束传播无关,所以修改了例子。
type fn = <T extends {a: string}>(arg: T) => voidconst f: fn = (arg) => console.log(arg.a)
同样不会报错,因为泛型T
被约束为类型{a: string}
所以获取变量arg
的a
属性并不会报错。
泛型中的类型推断infer
关键字
泛型中infer
配合extends
条件为真的子句可以提取类型,是通过类型生成类型的一种方式。
type Example<T> = T extends { x: infer R } ? R : never;type example = Example<{x: string}> // type example = string
infer R
可以提取x对应的类型string,infer使用时需要注意以下几点:
- infer只能和extends配合使用,无法单独使用
type Invalid<T> = infer R; // ❌ 错误:
infer必须配合
extends - infer只能使用在extends为真的子句内
type Example<T> = T extends { x: infer R } ? R : never;
ReturnType<Type> 的实现方式
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
泛型中的协变与逆变
高级内容待补充
总结:
- 泛型使用场景为关联多个变量类型,防止额外信息丢失。
- 泛型约束,泛型本身默认为
unknow
,添加约束后,将类型具象化为约束目标类型。 - 泛型配合关键字
extends
和infer
可以提取类型中的类型生成新的类型。 - 泛型中的逆变与协变研究中。