秒懂TypeScript的泛型

现在有一个函数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');

image.png

我们会发现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;
};

可以声明泛型的地方

  1. 第一种,在单个签名中声明泛型
type Filter = {
    <T>(array:T[],f:(item:T)=>boolean):T[]; 
}





let filter:Filter = (array,f)=>[];
// 在调用函数的时候绑定泛型,T的类型为Number
filter([1,2],(item)=>true);
  1. 第二种,覆盖全局签名的泛型
type Filter<T> = {
    (array:T[],f:(item:T)=>boolean):T[]; 
}





// 在声明Filter的函数时绑定T
let filter:Filter<number> = (array,f)=>[];
  1. 第三种,单个签名的简写(同第一种)
type Filter = <T>(array:T[],f:(item:T)=>boolean):T[]; 
let filter:Filter = (array,f)=>[];
// 在调用函数的时候绑定泛型,T的类型为Number
filter([1,2],(item)=>true);
  1. 第四种,覆盖全局签名的简写(同第二种)
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);
  1. 定义了一个Filter类型的函数filter
  2. 调用filter时传入了一个数组对象[1,2]和一个函数(item)=> item>1
  3. 根据函数的签名,TypeScript认定第一个参数为T类型的数组,而[1,2]又是一个number类型的数组,所以认定Tnumber类型
  4. 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){}
}
  1. 声明类时绑定作用于为整个类的泛型。K和V在MyMap的每个实例方法和实例属性中都可用。

  2. ? 构造函数中不能声明泛型。应该在类声明中声明泛型

  3. 在类内部,任何地方都能使用作用域为整个类的泛型。

  4. 实例方法可以访问类一级的泛型,也可以自己声明泛型。如merge方法就自己声明了一个泛型K1

  5. 静态方法不能访问类的泛型,这就像在值层面不能访问类的实例变量一样。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只接受一个参数,而这个参数至少必须包含sidessideLength

泛型的默认值

我们知道在使用函数的时候是可以给函数的参数一个默认值的,那么在使用泛型的时候也是可以给泛型一个默认值的。如最前面的例子

type Filter<T=number> = {
    (array:T[],f:(item:T)=>boolean):T[]; 
}





let filter:Filter = (array,f)=>[];

如果我们给了<T=number>,函数调用签名Filter的默认泛型就是number类型,那么在给filter的时候我们想用number类型时,就不用再次写了。

结尾

不知道你会用TS的泛型了吗?如果会了就快快用起来吧

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY0ucyV4' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片