目录

TS泛型的使用

TS范型的使用

1. 泛型是什么?有什么意义?

1.1 定义

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

1.2 意义

泛型使得组件的可重用性得到极大增强,使用泛型一个组件可以支持多种数据结构,开发者也可以自定义数据结构,大大提升了灵活性。

1.3 TS中的泛型

对于未知具体类型,但存在内在关联的数据结构,使用泛型可以充分表达约束,而又不陷入TS不必要的刻板,同时避免TypeScript成为AnyScript,起到提示和预防bug的效果,

下面结合实例,简单介绍TS泛型的使用。

2. TS泛型基本元素

2.1 泛型变量

泛型变量顾名思义也是一个变量,习惯上用T表示,语法上通<T>传递给函数或类,在参数列表中和返回值中直接使用

function echo<T>(anything: T): T {
    return anything;
}

可以将其理解为一个类型占位符,在使用它的时候,为避免出错应当把这些参数当做是任意或所有类型,而不能默认其具有某些类型特有的属性,如数组的长度等

如之前所说,实际上 T 可以用任何符合语法的名称代替。除了 T 之外,以下是常见泛型变量代表的意思:

  • K(Key):表示对象中的键类型;
  • V(Value):表示对象中的值类型;
  • E(Element):表示元素类型。

2.2 泛型函数

泛型函数使用像其他强类型语言的函数声明一样,以下是几种等价的写法:

function echo<T>(anything: T): T {
    return anything;
}

let another_echo: <T>(anything: T) => T = echo;

let yet_another_echo: {<T>(anything: T): T} = echo;

2.3 泛型接口

如果多个函数具有同样的泛型变量,或者可以泛化为统一的泛型函数,可以使用泛型接口抽象出一个统一的、可复用的泛型函数

interface echoFn {
    <T>(anything: T): T;
}

let echo_from_interface: echoFn = echo;

let echo_number: echoFn<number> = echo;
let echo_string: echoFn<number> = echo;

2.3 泛型类

泛型类在语法上没有太多新奇,同样使用<>括起泛型类型,跟在类名后面。

具体的使用场景,TS官网的例子很生动。

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

GenericNumber类,即广义数字的概念比较直观,它的类型不局限于number类型,也可以使用字符串或其它类型。


let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。

需要注意的是,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,类的静态属性不能使用这个泛型类型

2.4 泛型约束

文章开篇提到,在使用泛型变量时,应该将其视为任意或所有类型,但实际应用中我们需要抽离一些具有统一特征的数据结构,泛型本身不足以满足要求时,可以使用泛型约束进行细化。

如希望处理任意带有.length属性的类型,可以通过如下约束实现。

interface Length {
    length: number;
}

function echo<T extends Length>(anythingWithLength: T): T {
    console.log(anythingWithLength.length);
    return anythingWithLength;
}

3. TS泛型的操作

列举一些常用的TS泛型操作,用于简化泛型定义,其中type和interface的区别,另起一篇文章解释,暂且可以认为仅为语法不同。

3.1 类型别名

使用别名一方面可以简化重复书写,另一方面也使递归定义成为可能

//树的定义,第一步不完全正确
type Tree<T> = {
    value: T;          // value是T类型
    left: Tree<T>;     // 通过类型别名实现递归定义
    right: Tree<T>;
}

3.2 联合

泛型在与其他类型共同使用表示“或”关系时,可以使用联合操作符号。

//树的定义,修正
type Tree<T> = {
    value: T;          // value是T类型
    left: Tree<T> | null;     // 通过类型别名实现递归定义
    right: Tree<T> | null;
}

3.3 交叉

泛型在与其他类型共同使用表示“与”关系时,可以使用交叉操作符号。

//链表的定义,示意
type LinkedList<T> = T & { next: LinkedList<T> };