TypeScript - 泛型:初学者的指南
你好,未来的编程巨星!今天,我们将踏上一段令人兴奋的旅程,探索 TypeScript 泛型的世界。如果你是编程新手,不用担心——我会成为你的友好向导,我们会一步步来。在本教程结束时,你将能够像专业人士一样使用泛型!
什么是泛型,我们为什么要关心?
在我们深入细节之前,让我们从一个简单的类比开始。想象你有一个神奇的盒子,可以容纳任何类型的物品。有时你放入一本书,有时是一个玩具,甚至是一个三明治。这在 TypeScript 中本质上就是泛型的作用——它们允许我们创建灵活、可重用的代码,这些代码可以处理不同的类型。
问题示例
让我们看看几个泛型可以解决问题的场景:
- 你想要创建一个可以反转任何类型数组(数字、字符串、对象)的函数。
- 你需要一个可以存储和检索任何类型数据的类。
- 你正在构建一个应该适用于各种数据类型的实用函数。
如果没有泛型,你将不得不为每种数据类型编写单独的函数或类。这是大量的重复,正如任何优秀的程序员所知,重复是干净代码的敌人!
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
属性的类型一起工作。
泛型的优点
现在我们已经看到了泛型的作用,让我们总结一下它们的优点:
- 代码重用性:编写一次,多种类型可用。
- 类型安全:在编译时捕获类型相关的错误。
- 灵活性:创建可以处理各种数据类型的组件。
- 清晰性:明确输入和输出之间的关系。
泛型方法表
这里有一张常见泛型方法的便捷表格,你可能会遇到:
方法 | 描述 | 示例 |
---|---|---|
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