《TypeScript实战指南》—2.1.4 泛型

举报
华章计算机 发表于 2019/06/16 11:21:48 2019/06/16
【摘要】 本节书摘来自华章计算机《TypeScript实战指南》一书中的第2章,第2.1.4节,作者是胡桓铭。

2.1.4 泛型

泛型对于纯 JavaScript 程序员而言可能有点难以理解。

泛型用于提升代码的重用性。我们希望自己编写的代码,无论是模块还是组件,不仅能支持当前设计的数据类型,而且也能支持将来的数据类型。这在大型系统中是非常基础且重要的功能。所以我们常常能在各种各样的静态类型语言中看到泛型设计,使得用户可以灵活地选择希望支持的类型,而非局限于某一种类型。

1.泛型函数

下面我们来创建一个使用泛型的例子:hello 函数。这个函数非常简单,直接将输入的参数作为它的返回值。

不过,在这之前,我们不妨来假设一下,如果不使用泛型,会是什么样子。

比如,当我们需要传入一个数字的时候是如下这样:

function hello(arg: number): number {

    return arg;

}

但是很不幸,我们的需求很快就变更了,这个时候需要传入一个字符串,就会变成如下这样:

function hello(arg: string): string {

    return arg;

}

当然我们也可以使用 any 类型来表达这种混沌感:

function hello(arg: any): any {

    return arg;

}

但 any 类型并不能准确地表达返回值与参数必须是相同类型,因为 any 代表任何类型,这使得我们的类型表达开始混乱了。

因此,我们需要一种表达方式来控制函数的参数类型,这就要用到泛型了。

TypeScript 的泛型语法非常主流,与 Java 等静态类型语言的使用方式一致,就像下面这样:

function hello<T>(arg: T): T {

    return arg;

}

我们给 hello 函数添加了泛型变量T,T 代表用户即将传入的类型。类型既可以是 number,也可以是 string,而在最后,我们还使用 T 作为返回值的类型。这就达到了返回值和参数类型相同的目的,保持住了函数表达的准确性。

那如何使用泛型函数呢?跟之前的类型断言一样,有两种方法可以选择:

  • 使用熟悉的尖括号方式进行表达:

        let output = hello<string>("hello TypeScript");

我们在这里明确地指定T是string类型,并将它作为参数传给函数。当然,如果我们需要使用 number 类型,就直接在尖括号中写入 number 即可。

  • 使用类型推断。TypeScript 的编译器会根据传入的参数类型自动确定 T 的类型。这看上去非常智能,写起来是如下这样的:

        let output = hello("hello TypeScript");

你什么都不用做,像平常一样写代码即可。

这个时候编译器会明确地知道 T 的类型就是参数 "hello TypeScript" 的类型 string,同样,返回值类型是 T,也是 string 类型。

如果这个时候你使用的是 VSCode,将光标移至 output 上就会看到 string 的类型提示,如果再移动到 hello 头上,可以看到它的参数类型也写着 string。

类型推断帮助我们精简了代码,也提高了可读性,让代码变得简洁。在后续的章节中,我们还会体会到类型的好处。但凡事皆有例外,在某些特别复杂的情况下,类型推断是会失灵的,那么这个时候就需要我们非常明确地写出 T 的类型。

2.泛型变量

创建hello这样的泛型函数时,编译器要求在函数体中正确地使用类型。这听起来像是一句废话,换言之,你必须把这些参数当成任意类型。

让我们回顾一下 hello 这个函数:

function hello<T>(arg: T): T {

    return arg;

}

如果我们这个时候需要使用参数 arg 的长度时,就会出现下面这样尴尬的情况:

function hello<T>(arg: T): T {

    console.log(arg.length);  // Error: T doesn't have .length

    return arg;

}

编译器会非常迅速地进行报错,告诉我们泛型 T 并没有 length 这个属性。这似乎有点不合情理,如果 T 是 string 类型,那它为什么没有 length 属性呢。万一 T 是 number 类型呢?就像木桶原理一样,编译器会选择最糟糕的情况进行处理,T 代表任意类型,那么就一定会有最糟糕的情况。

什么情况下一定会有length 属性呢?可以使用泛型数组来表达这样的情况。由于我们操作的是数组,所以 length 属性一定是存在的,那就可以像普通的数组一样操作它:

function hello<T>(args: T[]): T[] {

    console.log(args.length);

    return arg;

}

这个时候,我们再使用 hello 函数时,就需要传入一个 T 的数组,也就是一个  string 变量的数组,或者 number 变量的数组,而不是一个单纯的变量。返回值也是同类型的数组。这可以让我们把泛型变量 T 作为数组的一部分属性,而不是作为整体类型,这增加了灵活性。

不只是使用中括号,还可以使用 Array 来表达数组,如下所示:

function hello<T>(arg: Array<T>): Array<T> {

    console.log(arg.length);

    return arg;

}

TypeScript 同时采用了这样一种在其他语言中非常常见的语法。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。