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