TypeScript 泛型

在前面章节的学习中,无论是定义变量、还是接口,我们都在做一件事情:把类型写死。比如使用了 name: string,那 name 就绝对不能是 number 类型。

这种极其严格的 “写死” 机制虽然保证了类型的安全,但在某些场景下,却极大地扼杀了代码的 “复用性”。

TypeScript 为什么需要泛型?

假设我们现在是一个底层工具库的开发者,需要写一个非常简单的函数:我们传给它什么参数,它就原封不动地返回什么结果。

如果使用以前学过的知识来写这个 echo() 函数,大概会写成下面这样:

function echo(arg: number): number {
    return arg;
}

那么问题就来了:现在这个函数只能接收并返回数字。如果业务小弟想要传入一个字符串,或者传入一个包含用户信息的对象,那么这个函数就直接废了。

为了让这个函数能够接收所有类型,很多大聪明小伙伴会很自然地祭出臭名昭著的 “Any 大法”:

function echo(arg: any): any {
    return arg;
}

const result = echo("Jack");

分析:

虽然这段代码不报错了,但却引发了一个致命的后果——TypeScript 类型推断彻底丢失。

当传入字符串 "Jack" 时,我们期望 result 是一个 string 类型,以便后续可以调用 result.length。但是,由于返回值被写死了成 any,TypeScript 编译器完全不知道 result 到底是个什么东西,因此后续代码中将失去所有的智能提示和安全检查。

TypeScript 泛型究竟是什么?

为了完美解决这个 “既要复用代码、又要保留类型推断” 的千古难题,TypeScript 引入了 “泛型(Generics)” 的概念。

什么是泛型呢?用一句话来解释就是:泛型就是专门用来装 “类型” 的变量

以前我们定义的普通变量,里面装的是具体的值(比如 666、true、"绿叶网" 等)。而泛型变量,里面装的是具体的类型(比如 string、number 等)。

在 TypeScript 中,我们可以用 “<T>” 来声明一个泛型变量。其中,使用了泛型变量的函数,也叫做 “泛型函数”。

语法:

function 函数名<泛型变量名>(参数名: 泛型变量名): 泛型变量名 {
    // 函数体逻辑
}

说明:

这里的 “T” 是 Type(类型)的首字母(非常重要)。

示例 1:使用泛型重写 echo() 函数

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

// 传入数字
const numResult = echo<number>(2026);
console.log(numResult);

// 传入字符串
const strResult = echo<string>("绿叶网"); 
console.log(strResult);

运行结果如下。

2026
绿叶网

分析:

在函数名后面加上 “<T>”,这就相当于声明了一个类型变量 “T”。然后我们就可以在参数类型和返回值类型中,使用这个 “T” 了。

需要注意的是,我们必须先在函数名后面加上 “<T>”,然后才能在 “参数类型” 和 “返回值类型” 中使用这个 “T”。

// 正确
function echo<T>(arg: T): T {
    return arg;
}

// 错误
function echo(arg: T): T {
    return arg;
}

在这个例子中,当我们调用 echo<string>("绿叶网") 时,就相当于把 string 这个 “类型” 赋值给了泛型变量 “T”。此时,这个 echo 函数在 TypeScript 编译器的眼里,就完全等价于:

function echo(arg: string): string {
    return arg;
}

TypeScript 泛型的类型推断

在上面的例子中,我们调用函数时手动传入了类型,比如 echo<number>(2026) 和 echo<string>("绿叶网")。

但在实际开发中,程序员都是比较 “懒” 的。我们可以借助 TypeScript 强大的类型推断能力,让编译器可以根据我们传入的实际参数,自动推导出泛型变量 “T” 的类型。

示例 2:利用类型推断简化

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

let numResult = echo(2026);
console.log(numResult);

let strResult = echo("绿叶网");
console.log(strResult);

运行结果如下。

2026
绿叶网

分析:

在这个例子中,我们省略了 <string> 和 <number>,让 TypeScript 自动推断类型。当我们写出 echo("绿叶网") 时,TypeScript 会根据传入的实际参数,自动推导出与之对应的类型。这样不仅省去了手动写类型的麻烦。

TypeScript 泛型字母的 “黑话” 指南

虽然我们可以使用任何字母或单词来命名泛型(比如 <MyType>),但在阅读 Vue 或 React 的底层源码时,我们会发现大佬们都非常喜欢使用单字母。这其实也是业界约定俗成的 “黑话”:

  • T (Type):代表通用的 “类型”(非常重要)。
  • K (Key):代表对象中的 “键” 的类型。
  • V (Value):代表对象中的 “值” 的类型。
  • E (Element):代表数组或集合中的 “元素” 类型。
  • U, S:通常紧跟在 T 后面,代表第 2 种、第 3 种类型。

掌握这套黑话,以后看各种框架的源码,我们就能一眼洞穿架构师的意图了。

TypeScript 泛型要学习哪些内容?

泛型是 TypeScript 极其重要的概念之一,在这一章中,我们需要学习以下内容:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号