现在有一个函数filter:从数组中筛选出符合条件的元素
// Filters是函数的调用签名
type Filters = {
(array:unknown,f:unknown): unknown[]
}
const filters:Filters = (array, f) => {
const result = [];
for (let i = 0; i < array.length; i++) {
const item = array[i];
f(item) && result.push(item);
}
return result;
};
函数的调用签名:
在TS中,可以像下面这样表示该函数的类型
(a:number,b:number) => number
这是TS表示函数类型的句法,也称为调用签名或者类型签名。
调用签名只包含类型层面的代码,即只有类型没有值。因此函数的调用签名可以表示参数的类型、this的类型、返回值的类型、剩余参数的类型和可选参数的类型,但是无法表示默认值。
如上filters的两个参数array和f都是unknown类型,但是我们知道,在使用TypeScript一般是不建议使用unknown的,那如果我们要写具体的类型,比如可以筛选数字数组、字符串数组、对象数组等,那应该怎么定义Filters呢?可以通过重载来处理
type Filters = {
(array:number[],f:(item:number)=>boolean): number[],
(array:string[],f:(item:string)=>boolean): string[],
(array:object[],f:(item:object)=>boolean): object[],
}
使用重载是解决了具体类型的定义,但是我们发现在针对数字数组和字符串数组是没有问题的,对于对象数组在使用的时候还是会报错,如下
const names = [
{
name: 'beth'
},
{
name: 'cat'
},
{
name: 'dog'
}
];
filter(names, item => item.name === 'cat');
我们会发现TypeScript会爆出一个错误,我们要访问的属性不存在,这是为什么呢?原来是因为object是无法描述对象的结构的,如果要访问到具体的属性,我们需要指明对象的具体结构。
重载的方式是无法解决我们的问题了,那要怎么解决呢?是的,用泛型就可以解决。
什么是泛型
泛型generic type 是指在定义函数、接口或者类型的时候,不预先指定具体的类型,用一个 占位类型来替代,而在使用的时候再置顶类型的一种特性。
泛型的作用与定义
泛型可以让函数、接口或者类的功能更具有一般性,比接受具体的类型函数更强大。
泛型使用尖括号<>定义,其中的参数是T,即<T>
。(T可以换成任何其他可以使用的名称,例如A,B,Zeb等,只不过按照惯例和大家的习惯使用的是T)
如何使用
函数的泛型
如最开始的例子中,如果使用泛型的话可以改成在函数调用签名的地方定义泛型:
type Filters<T> = {
(array:T[],f:(item:T)=>boolean): T[],
}
const filters:Filters<number> = (array, f) => {
const result = [];
for (let i = 0; i < array.length; i++) {
const item = array[i];
f(item) && result.push(item);
}
return result;
};
或者
type StringFilter = Filters<string>
const filter:StringFilter=(array, f) => {
const result = [];
for (let i = 0; i < array.length; i++) {
const item = array[i];
f(item) && result.push(item);
}
return result;
};
可以声明泛型的地方
- 第一种,在单个签名中声明泛型
type Filter = {
<T>(array:T[],f:(item:T)=>boolean):T[];
}
let filter:Filter = (array,f)=>[];
// 在调用函数的时候绑定泛型,T的类型为Number
filter([1,2],(item)=>true);
- 第二种,覆盖全局签名的泛型
type Filter<T> = {
(array:T[],f:(item:T)=>boolean):T[];
}
// 在声明Filter的函数时绑定T
let filter:Filter<number> = (array,f)=>[];
- 第三种,单个签名的简写(同第一种)
type Filter = <T>(array:T[],f:(item:T)=>boolean):T[];
let filter:Filter = (array,f)=>[];
// 在调用函数的时候绑定泛型,T的类型为Number
filter([1,2],(item)=>true);
- 第四种,覆盖全局签名的简写(同第二种)
type Filter<T> = (array:T[],f:(item:T)=>boolean):T[];
// 在声明Filter的函数时绑定T
let filter:Filter<number> = (array,f)=>[];
何时绑定泛型
在上面的四种函数定义泛型的地方也有写到何时绑定泛型的,现在就以第一种方式为例具体看一下:
type Filter = {
<T>(array:T[],f:(item:T)=>boolean):T[];
}
let filter:Filter = (array,f)=>[];
// 在调用函数的时候绑定泛型,T的类型为Number
filter([1,2],(item)=> item>1);
- 定义了一个Filter类型的函数filter
- 调用filter时传入了一个数组对象
[1,2]
和一个函数(item)=> item>1
- 根据函数的签名,TypeScript认定第一个参数为T类型的数组,而
[1,2]
又是一个number
类型的数组,所以认定T
为number
类型 - TypeScript检查全部代码,把T出现每一处都替换为number。
同理其他三种绑定泛型的也是差不多的逻辑,只不过绑定时机不一样。
泛型类
class MyMap<K,V>{
constructor(key:K,value:V){
// ...
};
get(key:K){};
set(key:K,value:V):void{};
merge<K1>(map:Map<K1,V>){};
static of<K,V>(k:K,v:V){}
}
-
声明类时绑定作用于为整个类的泛型。K和V在MyMap的每个实例方法和实例属性中都可用。
-
? 构造函数中不能声明泛型。应该在类声明中声明泛型
-
在类内部,任何地方都能使用作用域为整个类的泛型。
-
实例方法可以访问类一级的泛型,也可以自己声明泛型。如
merge
方法就自己声明了一个泛型K1
-
静态方法不能访问类的泛型,这就像在值层面不能访问类的实例变量一样。
of
中声明的K,V
是自己的泛型,只不过与类的泛型用了一样的名字
泛型接口
同函数和类一样,接口也可以绑定泛型
interface MyMap<T,V> {
get(key:T)
set(key:T,val:V):V
}
受限的泛型
现在有一个二叉树,把节点分为三类
type TreeNode = {
value: string
}
// 没有子节点的TreeNode
type LeafNode = TreeNode & {
isLeaf: true
}
// 有子节点的TreeNode
type InnerNode = TreeNode & {
children: [TreeNode] | [TreeNode]
}
想象一下,现在有一个函数mapNode,这个函数呢只有传入TreeNode或其子类型才可以,一旦传入{}、null或者TreeNode数组等,TS就会立即提示错误,那么此时mapNode的泛型如何定义呢?
是的,就是使用extends来约束:
function mapNode<T extends TreeNode>(node: T):T {
}
为什么要这么做呢?
是因为T extends TreeNode
时,T会被认为是TreeNode的子类型,会在输入节点类型时将得到保留,映射后类型也不变。
当然你可能会提出,只有这样一个限制可能不能满足需求,如果想要受多个限制要怎么做呢?
当然是使用交集(&)来限制啦:
type HasSides = {sides: number}
type Length = { sideLength: number}
function logP<Shape extends HasSides & Length>(s: Shape): Shape {}
函数logP
只接受一个参数,而这个参数至少必须包含sides
和sideLength
泛型的默认值
我们知道在使用函数的时候是可以给函数的参数一个默认值的,那么在使用泛型的时候也是可以给泛型一个默认值的。如最前面的例子
type Filter<T=number> = {
(array:T[],f:(item:T)=>boolean):T[];
}
let filter:Filter = (array,f)=>[];
如果我们给了<T=number>
,函数调用签名Filter的默认泛型就是number类型,那么在给filter的时候我们想用number类型时,就不用再次写了。
结尾
不知道你会用TS的泛型了吗?如果会了就快快用起来吧