TypeScript - 泛型:初学者的指南

你好,未来的编程巨星!今天,我们将踏上一段令人兴奋的旅程,探索 TypeScript 泛型的世界。如果你是编程新手,不用担心——我会成为你的友好向导,我们会一步步来。在本教程结束时,你将能够像专业人士一样使用泛型!

TypeScript - Generics

什么是泛型,我们为什么要关心?

在我们深入细节之前,让我们从一个简单的类比开始。想象你有一个神奇的盒子,可以容纳任何类型的物品。有时你放入一本书,有时是一个玩具,甚至是一个三明治。这在 TypeScript 中本质上就是泛型的作用——它们允许我们创建灵活、可重用的代码,这些代码可以处理不同的类型。

问题示例

让我们看看几个泛型可以解决问题的场景:

  1. 你想要创建一个可以反转任何类型数组(数字、字符串、对象)的函数。
  2. 你需要一个可以存储和检索任何类型数据的类。
  3. 你正在构建一个应该适用于各种数据类型的实用函数。

如果没有泛型,你将不得不为每种数据类型编写单独的函数或类。这是大量的重复,正如任何优秀的程序员所知,重复是干净代码的敌人!

TypeScript 泛型来拯救!

现在,让我们卷起袖子,看看泛型在实际中是如何工作的。

基本泛型函数

这里有一个简单的泛型函数,可以处理任何类型:

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

让我们分解一下:

  • <T> 是我们的类型参数。它就像是我们将要使用的类型的占位符。
  • (arg: T) 意味着我们的函数接受一个类型为 T 的参数。
  • : T 在括号后面意味着我们的函数将返回一个类型为 T 的值。

我们可以这样使用这个函数:

let output1 = identity<string>("你好,泛型!");
let output2 = identity<number>(42);

console.log(output1); // "你好,泛型!"
console.log(output2); // 42

酷炫吧?同一个函数适用于不同的类型!

泛型接口

我们也可以在接口中使用泛型。这里有一个例子:

interface GenericBox<T> {
contents: T;
}

let stringBox: GenericBox<string> = { contents: "一条秘密信息" };
let numberBox: GenericBox<number> = { contents: 123 };

console.log(stringBox.contents); // "一条秘密信息"
console.log(numberBox.contents); // 123

我们的 GenericBox 可以容纳任何类型的内容。就像我们之前提到的那个神奇的盒子!

泛型类

让我们创建一个泛型类,它可以作为一个简单数据存储:

class DataStore<T> {
private data: T[] = [];

addItem(item: T): void {
this.data.push(item);
}

getItems(): T[] {
return this.data;
}
}

let stringStore = new DataStore<string>();
stringStore.addItem("你好");
stringStore.addItem("世界");
console.log(stringStore.getItems()); // ["你好", "世界"]

let numberStore = new DataStore<number>();
numberStore.addItem(1);
numberStore.addItem(2);
console.log(numberStore.getItems()); // [1, 2]

这个 DataStore 类可以存储和检索任何类型的数据。相当方便,不是吗?

泛型约束

有时,我们想要限制可以与我们的泛型一起使用的类型。我们可以通过约束来实现:

interface Lengthy {
length: number;
}

function logLength<T extends Lengthy>(arg: T): void {
console.log(arg.length);
}

logLength("你好"); // 6
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
// logLength(123); // 错误:数字没有 length 属性

在这里,我们的 logLength 函数只能与具有 length 属性的类型一起工作。

泛型的优点

现在我们已经看到了泛型的作用,让我们总结一下它们的优点:

  1. 代码重用性:编写一次,多种类型可用。
  2. 类型安全:在编译时捕获类型相关的错误。
  3. 灵活性:创建可以处理各种数据类型的组件。
  4. 清晰性:明确输入和输出之间的关系。

泛型方法表

这里有一张常见泛型方法的便捷表格,你可能会遇到:

方法 描述 示例
Array.map<U>() 转换数组元素 [1, 2, 3].map<string>(n => n.toString())
Promise.all<T>() 等待所有承诺解决 Promise.all<number>([Promise.resolve(1), Promise.resolve(2)])
Object.keys<T>() 获取对象键作为数组 Object.keys<{name: string}>({name: "Alice"})
JSON.parse<T>() 解析 JSON 字符串为对象 JSON.parse<{age: number}>('{"age": 30}')

结论

恭喜你!你已经迈出了进入 TypeScript 泛型奇妙世界的第一步。记住,像任何强大的工具一样,泛型一开始可能感觉有点棘手,但随着实践,它们将变得习以为常。

在你继续编程旅程时,你会发现泛型就像你 TypeScript 工具箱中的瑞士军刀——多功能、强大且极其有用。所以,继续前进,年轻的原力使用者,愿泛型与你同在!

快乐编程,直到下一次,保持探索和学习!

Credits: Image by storyset