Typescript|Generic
Intro
定义
带有类型约束的语言需要泛型(Generic)来实现更加通用、符合标准、可复用的逻辑
在不使用泛型的时候吗,我们确实是可以用 any 作为函数的入参类型约束,但是这个就和 Object 作为入参以及返回值一样,没有类型限制时,外部在调用这段函数时,无法明确确定入参类型,也无法明确确定返回值类型
1 |
|
1 |
|
我们可以尝试为这个identity
函数引入一个Type变量,这个Type变量和常规变量不同,只作用在类型上而不是js中通常的值上
1 |
|
针对这个函数的签名
1 |
|
在函数的签名处定义了作用于类型的Type变量,由于此处只有一个类型变量Type,因此这段函数就是将入参类型和返回值类型都和Type变量类型绑定上
调用
在调用泛型函数时,有两种方式:
- 显式声明类型
1 |
|
- 隐式类型推断(更常见)
编译器根据我们传入的参数类型自动为我们设置 类型变量 Type
的值
1 |
|
这种一般IDE都会在隐式推断计算后(IDE侧)自动将类型标注出来
小总结
我们可以将这样的泛型函数理解为:
这个函数不仅接受一个参数arg,还接受一个类型参数Type
我们在传入参数的时候会自动将变量类型 和 Type 进行绑定
我们在函数中可以使用绑定好的类型参数Type,更大程度上提供了灵活性
泛型函数类型变量
非泛型函数类型变量
在了解泛型函数类型的变量定义之前,我们需要对非泛型的函数类型的变量有一定的了解
例如有这么一个函数
1 |
|
我们想要将这个函数作为一种类型赋值给一个新的变量,这种没有任何泛型出现
1 |
|
这段表达式比较复杂,需要这么理解:
1 |
|
(x: number, y: number) => number
在 ts 中表示一种函数类型,接收两个 number 作为入参,返回一个 number
简单来说,非泛型函数类型变量的定义格式就是:
let 函数名: (参数类型列表) => 返回值类型 = 实际函数;
泛型函数类型变量
知道了非泛型的,带泛型的和非泛型的很相似
例如有这么一个函数
1 |
|
带有泛型类型的这个函数类型就应该定义为:
1 |
|
当然这个 类型变量Type 名称是随意的
1 |
|
函数接口
定义带泛型函数接口
上面的代码<Input>(arg: Input) => Input
就定义了一个函数类型,我们可以将其抽取到一个接口中,这种就是函数接口
1 |
|
后续我们可以直接引用这个函数接口来替代 带有泛型的函数的类型变量
1 |
|
将泛型参数提升至函数接口侧
在更多的情况下,我们可能会将泛型的声明定义在接口侧
1 |
|
两种写法总结对比
两种写法,区别在于泛型参数究竟是放在函数侧,还是函数接口侧
直接举个例子更容易理解
1 |
|
在上述代码中,一旦我们定义了 let interfaceIdentityWithGeneric: IdentityFnWithGeneric<string> = identity;
则表示这个新的函数类型变量,在调用的时候只能传入 string 类型的参数,返回的也只会是 string 类型,接口内部所有成员都能共享这个类型参数。
通过这种方式,可以让使用者在使用接口时明确地指定类型,更加直观
用法场景 | 泛型在函数上 <T>(arg: T) => T |
泛型在接口上 interface Foo<T> |
---|---|---|
每次调用可能使用不同类型 | ✅ 推荐使用 | ❌ 不适合 |
希望使用时一次性指定固定类型 | ❌ 不够清晰 | ✅ 推荐使用 |
接口内有多个成员都要用这个类型 | ❌ 不方便 | ✅ 类型统一,语义清晰 |
泛型类
ts中的class更偏oop一些,但是通过泛型类我们可以更好地理解上面的内容中将泛型提升至接口侧带来的便利性
1 |
|
将类型参数放在类本身上,可以确保类的所有属性都使用相同的类型
类型约束
这一部分其实和别的编程语言也差不太多,我们很多时候希望类型参数绑定的类型并不是表示任何类型,而是有一定的约束
例如我们希望在函数内部对类型参数的变量调用指定的length字段
1 |
|
如果此时我们在外部调用的时候传入了一个不符合约束的类型变量,同样也会报错:
1 |
|
1 |
|