引言:什么是泛型,为什么它们很重要?
\n\n在学习 TypeScript 时,你很快就会遇到需要编写一个可以处理不同数据类型的函数或类的情况。如果没有专门的工具,你只能为每种类型重复代码,或者使用 any,但这会破坏严格类型化的所有魔力。这时,泛型就派上用场了。
泛型 是 TypeScript 中一种强大的机制,它允许你创建能够处理不同类型、同时保持严格类型化的组件(函数、类、接口)。想象一下,你正在编写一个通用容器,它可以存放苹果、书籍和数字,但 TypeScript 确切地知道每个具体情况下里面装的是什么。
\n\n在本文中,我们将探讨为什么需要泛型,以及如何在真实项目中声明和使用它们。你将学习如何使你的代码更灵活、更安全、更可复用。
\n\n泛型基础:语法和第一个函数
\n\n让我们从一个简单的例子开始。假设我们需要一个函数,它接受一个参数并返回它本身(恒等函数)。没有泛型,我们会这样写:
\n\nfunction identity(arg: number): number {\n return arg;\n}\n\n但这个函数只适用于数字。为了让它通用,我们可以使用 any:
function identity(arg: any): any {\n return arg;\n}\n\n问题在于我们丢失了类型信息。TypeScript 不知道返回的正是传入的类型。这时,泛型就派上用场了:
\n\nfunction identity<T>(arg: T): T {\n return arg;\n}\n\n// 使用方式:\nlet output1 = identity<string>("你好"); // 类型为 string\nlet output2 = identity<number>(42); // 类型为 number\n\n我们来分析一下语法:
\n- \n
<T>— 这是类型参数。你可以使用任何字母,但按照惯例,最常用的是T(Type)、U、V。 \n arg: T— 参数的类型为T。 \n - 括号后的
: T— 函数返回类型为T的值。 \n
现在 TypeScript 知道:如果你传入一个字符串,那么输出也将是字符串。如果是数字,则输出数字。类型推断通常会自动工作,因此你可以直接写 identity("你好") — TypeScript 会自动理解 T 是 string。
函数中的泛型:处理数组和约束
\n\n泛型最常用于处理数组或集合。考虑一个返回数组第一个元素的函数:
\n\nfunction getFirst<T>(arr: T[]): T | undefined {\n return arr[0];\n}\n\nconst numbers = [1, 2, 3];\nconst firstNumber = getFirst(numbers); // 类型为 number\n\nconst strings = ["a", "b", "c"];\nconst firstString = getFirst(strings); // 类型为 string\n\n请注意:T[] 是 Array<T> 的简写。该函数适用于任何数组,但返回的元素类型正是数组中存储的类型。
使用 extends 进行约束
\n\n有时我们需要类型 T 具有某些属性。例如,我们想获取对象的长度。并非所有类型都有 length 属性,因此我们可以施加约束:
interface HasLength {\n length: number;\n}\n\nfunction getLength<T extends HasLength>(arg: T): number {\n return arg.length;\n}\n\ngetLength("字符串"); // 正确,字符串有 length\ngetLength([1, 2, 3]); // 正确,数组有 length\ngetLength(42); // 错误!数字没有 length