泛型
概述
泛型(Generics)是 TypeScript 中最重要的特性之一,它允许我们创建可重用的组件,这些组件可以处理多种数据类型,而不是单一的数据类型。通过泛型,我们可以编写更灵活、更可复用的代码,同时保持类型安全。泛型就像类型变量,可以在定义时不指定具体类型,而在使用时再指定,这样可以让代码更加通用和强大。
为什么需要泛型
在介绍泛型语法之前,让我们先看看不使用泛型时遇到的问题:
typescript
// 不使用泛型:需要为每种类型创建单独的函数
function getStringValue(value: string): string {
return value;
}
function getNumberValue(value: number): number {
return value;
}
function getBooleanValue(value: boolean): boolean {
return value;
}
// 使用 any:失去了类型安全
function getValue(value: any): any {
return value;
}
const str = getValue("hello"); // 类型是 any,失去了类型检查
const num = getValue(42); // 类型是 any,失去了类型检查使用泛型可以解决这个问题:
typescript
// 使用泛型:一个函数处理所有类型,同时保持类型安全
function getValue<T>(value: T): T {
return value;
}
const str = getValue<string>("hello"); // 类型是 string
const num = getValue<number>(42); // 类型是 number
const bool = getValue<boolean>(true); // 类型是 boolean基本语法
泛型函数
泛型函数使用尖括号 <T> 来声明类型参数,T 是一个类型变量,可以是任何有效的标识符:
typescript
// 基本泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
const str = identity<string>("hello"); // 类型:string
const num = identity<number>(42); // 类型:number
const bool = identity<boolean>(true); // 类型:boolean
// TypeScript 可以自动推断类型
const str2 = identity("hello"); // 自动推断为 string
const num2 = identity(42); // 自动推断为 number提示
TypeScript 通常可以自动推断泛型类型参数,所以很多时候可以省略类型参数,让 TypeScript 自动推断。
多个类型参数
泛型函数可以有多个类型参数:
typescript
// 多个类型参数的泛型函数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
// 使用示例
const stringNumber = pair<string, number>("hello", 42);
// 类型:[string, number]
const numberBoolean = pair<number, boolean>(10, true);
// 类型:[number, boolean]
// 自动推断
const auto = pair("hello", 42);
// 类型:[string, number]泛型函数类型
可以定义泛型函数类型:
typescript
// 定义泛型函数类型
type GenericFunction<T> = (arg: T) => T;
// 使用泛型函数类型
const identity: GenericFunction<string> = (arg) => arg;
const result = identity("hello"); // 类型:string
// 更复杂的泛型函数类型
type Mapper<T, R> = (item: T) => R;
type Filter<T> = (item: T) => boolean;
const stringToNumber: Mapper<string, number> = (str) => str.length;
const isEven: Filter<number> = (num) => num % 2 === 0;泛型接口
接口也可以使用泛型,让接口更加灵活:
typescript
// 基本泛型接口
interface Box<T> {
value: T;
}
// 使用泛型接口
const stringBox: Box<string> = {
value: "hello"
};
const numberBox: Box<number> = {
value: 42
};
// 泛型接口方法
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}
// 实现泛型接口
class StringContainer implements Container<string> {
value: string;
constructor(value: string) {
this.value = value;
}
getValue(): string {
return this.value;
}
setValue(value: string): void {
this.value = value;
}
}多个类型参数的接口
接口也可以有多个类型参数:
typescript
// 多个类型参数的泛型接口
interface KeyValuePair<K, V> {
key: K;
value: V;
}
// 使用示例
const pair1: KeyValuePair<string, number> = {
key: "age",
value: 30
};
const pair2: KeyValuePair<number, string> = {
key: 1,
value: "first"
};
// 在函数中使用
function createPair<K, V>(key: K, value: V): KeyValuePair<K, V> {
return { key, value };
}
const pair3 = createPair("name", "Alice");
// 类型:KeyValuePair<string, string>泛型类
类也可以使用泛型,让类更加通用:
typescript
// 基本泛型类
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
// 使用泛型类
const stringBox = new Box<string>("hello");
console.log(stringBox.getValue()); // "hello"
const numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // 42
// 自动推断
const autoBox = new Box("hello"); // 推断为 Box<string>泛型类的静态成员
静态成员不能使用类的泛型类型参数,但可以有自己的泛型:
typescript
class Utility {
// 静态方法可以使用自己的泛型
static create<T>(value: T): T {
return value;
}
// 静态方法可以有多个类型参数
static pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
}
// 使用静态方法
const value = Utility.create<string>("hello");
const pair = Utility.pair<string, number>("hello", 42);使用示例
示例 1:数组工具函数
typescript
// 泛型数组工具函数
function getFirstElement<T>(array: T[]): T | undefined {
return array.length > 0 ? array[0] : undefined;
}
function getLastElement<T>(array: T[]): T | undefined {
return array.length > 0 ? array[array.length - 1] : undefined;
}
function reverseArray<T>(array: T[]): T[] {
return [...array].reverse();
}
// 使用示例
const numbers = [1, 2, 3, 4, 5];
const strings = ["a", "b", "c", "d"];
const firstNumber = getFirstElement(numbers); // 类型:number | undefined
const lastString = getLastElement(strings); // 类型:string | undefined
const reversedNumbers = reverseArray(numbers); // 类型:number[]示例 2:API 响应处理
typescript
// 定义 API 响应接口
interface ApiResponse<T> {
status: "success" | "error";
data?: T;
message?: string;
code?: number;
}
// 泛型 API 处理函数
function handleApiResponse<T>(response: ApiResponse<T>): T | null {
if (response.status === "success" && response.data) {
return response.data;
}
return null;
}
// 使用示例
interface User {
id: number;
name: string;
email: string;
}
// 模拟 API 响应
const userResponse: ApiResponse<User> = {
status: "success",
data: {
id: 1,
name: "Alice",
email: "alice@example.com"
}
};
const user = handleApiResponse(userResponse);
// 类型:User | null
if (user) {
console.log(user.name); // 类型安全,可以访问 User 的属性
}示例 3:缓存系统
typescript
// 泛型缓存类
class Cache<T> {
private data: Map<string, T> = new Map();
set(key: string, value: T): void {
this.data.set(key, value);
}
get(key: string): T | undefined {
return this.data.get(key);
}
has(key: string): boolean {
return this.data.has(key);
}
delete(key: string): boolean {
return this.data.delete(key);
}
clear(): void {
this.data.clear();
}
}
// 使用示例
// 字符串缓存
const stringCache = new Cache<string>();
stringCache.set("name", "Alice");
const name = stringCache.get("name"); // 类型:string | undefined
// 数字缓存
const numberCache = new Cache<number>();
numberCache.set("count", 42);
const count = numberCache.get("count"); // 类型:number | undefined
// 对象缓存
interface Product {
id: number;
name: string;
price: number;
}
const productCache = new Cache<Product>();
productCache.set("product-1", {
id: 1,
name: "Laptop",
price: 999
});
const product = productCache.get("product-1");
// 类型:Product | undefined示例 4:数据转换工具
typescript
// 泛型数据转换函数
function mapArray<T, R>(
array: T[],
mapper: (item: T, index: number) => R
): R[] {
return array.map(mapper);
}
function filterArray<T>(
array: T[],
predicate: (item: T) => boolean
): T[] {
return array.filter(predicate);
}
function reduceArray<T, R>(
array: T[],
reducer: (accumulator: R, item: T, index: number) => R,
initialValue: R
): R {
return array.reduce(reducer, initialValue);
}
// 使用示例
const numbers = [1, 2, 3, 4, 5];
// 映射:将数字转换为字符串
const strings = mapArray(numbers, (num) => num.toString());
// 类型:string[]
// 过滤:获取偶数
const evens = filterArray(numbers, (num) => num % 2 === 0);
// 类型:number[]
// 归约:计算总和
const sum = reduceArray(numbers, (acc, num) => acc + num, 0);
// 类型:number示例 5:事件系统
typescript
// 泛型事件处理器接口
interface EventHandler<T> {
(event: T): void;
}
// 泛型事件发射器类
class EventEmitter<T> {
private handlers: EventHandler<T>[] = [];
on(handler: EventHandler<T>): void {
this.handlers.push(handler);
}
off(handler: EventHandler<T>): void {
const index = this.handlers.indexOf(handler);
if (index > -1) {
this.handlers.splice(index, 1);
}
}
emit(event: T): void {
this.handlers.forEach(handler => handler(event));
}
}
// 使用示例
// 字符串事件
interface StringEvent {
message: string;
timestamp: number;
}
const stringEmitter = new EventEmitter<StringEvent>();
stringEmitter.on((event) => {
console.log(event.message); // 类型安全
});
stringEmitter.emit({
message: "Hello, World!",
timestamp: Date.now()
});
// 数字事件
const numberEmitter = new EventEmitter<number>();
numberEmitter.on((value) => {
console.log(value * 2); // 类型安全,知道 value 是 number
});
numberEmitter.emit(42);泛型约束
虽然我们还没有详细学习泛型约束(将在下一章介绍),但这里先简单了解一下:
typescript
// 使用 extends 约束泛型类型
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
// 可以用于字符串、数组等有 length 属性的类型
const strLength = getLength("hello"); // 5
const arrLength = getLength([1, 2, 3]); // 3
// ❌ 错误:数字没有 length 属性
// const numLength = getLength(42); // Error信息
关于泛型约束的详细内容,请参考泛型约束章节。
类型推断
TypeScript 在大多数情况下可以自动推断泛型类型:
typescript
// 自动推断类型参数
function identity<T>(arg: T): T {
return arg;
}
// 不需要显式指定类型
const str = identity("hello"); // 推断为 string
const num = identity(42); // 推断为 number
const bool = identity(true); // 推断为 boolean
// 复杂类型的推断
function createPair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const pair = createPair("hello", 42);
// 推断为 [string, number]需要显式指定类型的情况
有些情况下需要显式指定类型参数:
typescript
// 当类型推断不明确时
function createArray<T>(length: number, value: T): T[] {
return Array(length).fill(value);
}
// 需要显式指定类型
const strings = createArray<string>(5, "hello");
// 如果不指定,可能推断为 unknown[]
// 当函数返回类型依赖于类型参数时
function parseJSON<T>(json: string): T {
return JSON.parse(json);
}
// 需要显式指定返回类型
const user = parseJSON<User>('{"id": 1, "name": "Alice"}');
// 如果不指定,返回类型是 any类型检查示例
常见错误
typescript
// ❌ 错误:类型不匹配
function identity<T>(arg: T): T {
return arg;
}
const str = identity<string>("hello");
const num: number = str; // Error: Type 'string' is not assignable to type 'number'
// ❌ 错误:缺少类型参数(在某些情况下)
function createArray<T>(length: number, value: T): T[] {
return Array(length).fill(value);
}
const arr = createArray(5, "hello"); // 可能推断为 unknown[],需要显式指定
// ❌ 错误:泛型类型参数不能用于静态成员
class BadExample<T> {
// Error: Static members cannot reference class type parameters
static value: T;
}正确写法
typescript
// ✅ 正确:类型匹配
function identity<T>(arg: T): T {
return arg;
}
const str = identity<string>("hello"); // 类型:string
const num = identity<number>(42); // 类型:number
// ✅ 正确:自动推断
const str2 = identity("hello"); // 推断为 string
const num2 = identity(42); // 推断为 number
// ✅ 正确:多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair<string, number>("hello", 42);
// 类型:[string, number]
// ✅ 正确:静态方法使用自己的泛型
class Utility {
static create<T>(value: T): T {
return value;
}
}注意事项
提示
- 泛型让代码更加灵活和可复用,同时保持类型安全
- TypeScript 通常可以自动推断泛型类型参数,不需要总是显式指定
- 使用有意义的类型参数名称(如
T、U、K、V)可以提高代码可读性 - 泛型可以用于函数、接口、类和类型别名
注意
- 静态成员不能使用类的泛型类型参数,但可以有自己的泛型
- 在某些情况下,需要显式指定类型参数以确保类型推断正确
- 过度使用泛型可能会让代码变得复杂,需要权衡可复用性和可读性
- 泛型类型参数只在编译时存在,运行时会被擦除
重要
- 泛型是 TypeScript 类型系统的核心特性,理解泛型对于掌握 TypeScript 非常重要
- 泛型与类型约束、条件类型等高级特性结合使用,可以构建强大的类型系统
- 泛型不会影响运行时的性能,因为类型信息在编译时会被擦除
信息
泛型的优势:
- 类型安全:在编译时捕获类型错误
- 代码复用:一个函数/类可以处理多种类型
- 更好的 IDE 支持:自动补全和类型提示
- 自文档化:类型信息本身就是文档
泛型的应用场景:
- 数据结构(数组、链表、栈、队列等)
- API 响应处理
- 工具函数库
- 框架和库的开发
- 数据转换和映射