Skip to content

泛型

概述

泛型(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 通常可以自动推断泛型类型参数,不需要总是显式指定
  • 使用有意义的类型参数名称(如 TUKV)可以提高代码可读性
  • 泛型可以用于函数、接口、类和类型别名

注意

  • 静态成员不能使用类的泛型类型参数,但可以有自己的泛型
  • 在某些情况下,需要显式指定类型参数以确保类型推断正确
  • 过度使用泛型可能会让代码变得复杂,需要权衡可复用性和可读性
  • 泛型类型参数只在编译时存在,运行时会被擦除

重要

  • 泛型是 TypeScript 类型系统的核心特性,理解泛型对于掌握 TypeScript 非常重要
  • 泛型与类型约束、条件类型等高级特性结合使用,可以构建强大的类型系统
  • 泛型不会影响运行时的性能,因为类型信息在编译时会被擦除

信息

泛型的优势

  • 类型安全:在编译时捕获类型错误
  • 代码复用:一个函数/类可以处理多种类型
  • 更好的 IDE 支持:自动补全和类型提示
  • 自文档化:类型信息本身就是文档

泛型的应用场景

  • 数据结构(数组、链表、栈、队列等)
  • API 响应处理
  • 工具函数库
  • 框架和库的开发
  • 数据转换和映射

相关链接

基于 VitePress 构建