TypeScript 中的泛型简介
TypeScript 是一种强类型语言,通过添加静态类型来扩展 JavaScript 的功能。然而,其最强大且灵活的特性之一是 Generics(泛型) 机制。如果你曾经编写过需要处理不同数据类型但同时保持类型安全的代码,那么泛型正是你所需要的。
想象一下,你正在创建一个通用函数,它可以接受数字、字符串或对象的数组。如果没有泛型,你只能使用 any 类型(这会破坏 TypeScript 的所有优势),或者为每种类型编写单独的函数。泛型优雅且安全地解决了这个问题。在本文中,我们将探讨什么是泛型、为什么需要它们,以及如何在实际项目中高效使用它们。
什么是泛型以及为什么需要它们?
Generics 是一种机制,它允许创建能够处理不同数据类型且不丢失类型信息的组件(函数、类、接口)。你无需固定具体类型,而是使用一个 类型参数,该参数在调用时被替换。
没有泛型的问题
假设你想编写一个返回数组第一个元素的函数。没有泛型时,它可能看起来像这样:
function getFirstElement(arr: any[]): any { return arr[0];}
const num = getFirstElement([1, 2, 3]); // 类型为 'any' — 我们丢失了信息console.log(num.toUpperCase()); // 运行时错误,但 TypeScript 保持沉默这里我们使用了 any,TypeScript 无法警告我们 num 是数字而不是字符串。这会导致潜在的运行时错误。
使用泛型的解决方案
现在,我们用泛型重写同一个函数:
function getFirstElement<T>(arr: T[]): T { return arr[0];}
const num = getFirstElement([1, 2, 3]); // 类型为 'number'const str = getFirstElement(['a', 'b', 'c']); // 类型为 'string'
// num.toUpperCase(); // 编译错误 — TypeScript 知道 num 是数字符号 <T> 是一个类型参数。它充当占位符:当我们用数字数组调用函数时,T 变为 number,返回值也获得 number 类型。这样,我们在不损失灵活性的情况下保持了强类型。
如何使用泛型:主要场景
泛型不仅用于函数,还用于类、接口和类型。让我们看看最常见的场景。
函数中的泛型
最简单且最常见的情况是处理不同类型数据的函数。例如,创建一个键值对的函数:
function createPair<K, V>(key: K, value: V): { key: K; value: V } { return { key, value };}
const pair = createPair('id', 123); // { key: string; value: number }console.log(pair.key.toUpperCase()); // 正常工作// pair.key.toFixed(); // 错误 — key 是字符串这里我们使用了两个类型参数:K 用于键,V 用于值。TypeScript 会根据参数自动推断类型。
类中的泛型
泛型非常适合创建通用类,例如栈、队列或数据存储:
class Stack<T> { private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items[this.items.length - 1]; }}
const numberStack = new Stack<number>();numberStack.push(10);numberStack.push(20);console.log(numberStack
```