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>("Hello, Generics!");
let output2 = identity<number>(42);

console.log(output1); // "Hello, Generics!"
console.log(output2); // 42

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

泛型接口

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

interface GenericBox<T> {
contents: T;
}

let stringBox: GenericBox<string> = { contents: "A secret message" };
let numberBox: GenericBox<number> = { contents: 123 };

console.log(stringBox.contents); // "A secret message"
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("Hello");
stringStore.addItem("World");
console.log(stringStore.getItems()); // ["Hello", "World"]

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("Hello"); // 5
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