折腾侠
技术教程

TypeScript 泛型完全指南:从入门到实战

折腾侠
2026/03/25 发布
21约 11 分钟1632 字 / 1461 词00

TypeScript 泛型完全指南:从入门到实战

引言

在 TypeScript 的世界里,泛型(Generics)是最强大但也最容易被误解的特性之一。很多开发者在学习 TypeScript 时,会在泛型这里卡住,觉得它过于抽象和复杂。但实际上,泛型是编写可复用、类型安全代码的关键工具。

本文将带你从泛型的基础概念开始,逐步深入到实际应用场景,帮助你真正掌握这一重要特性。无论你是 TypeScript 初学者还是有一定经验的开发者,相信都能从本文中获得收获。

什么是泛型?

泛型,简单来说,就是"类型的参数化"。就像函数可以接受参数一样,泛型允许我们给类型也加上参数。这样,我们就可以编写能够适用于多种类型的代码,而不需要为每种类型都写一份重复的代码。

为什么需要泛型?

让我们先看一个没有使用泛型的例子:

TypeScript
// 没有泛型的情况
function identityString(arg: string): string {
  return arg;
}

function identityNumber(arg: number): number {
  return arg;
}

function identityBoolean(arg: boolean): boolean {
  return arg;
}

上面的代码中,我们为了处理不同类型的参数,不得不编写三个几乎完全相同的函数。这不仅造成了代码冗余,而且如果我们需要支持更多类型,就要继续添加更多的函数。

使用泛型后,我们可以这样写:

TypeScript
// 使用泛型
function identity<T>(arg: T): T {
  return arg;
}

// 使用示例
const result1 = identity<string>('Hello');  // 类型:string
const result2 = identity<number>(42);       // 类型:number
const result3 = identity<boolean>(true);    // 类型:boolean

通过泛型,我们只用一个函数就实现了之前三个函数的功能,而且类型信息完全保留。

泛型基础语法

泛型函数

泛型函数的基本语法是在函数名后面用尖括号声明类型参数:

TypeScript
function identity<T>(arg: T): T {
  return arg;
}

这里的 INLINE_CODE_0 就是一个类型参数,它代表"某种类型"。在使用函数时,我们可以指定具体的类型:

TypeScript
const result = identity<string>('Hello TypeScript');

TypeScript 也支持类型推断,很多时候不需要显式指定类型:

TypeScript
const result = identity('Hello TypeScript');  // T 被推断为 string

多个类型参数

泛型函数可以有多个类型参数:

TypeScript
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const result = pair<string, number>('age', 25);  // 类型:[string, number]

泛型接口

泛型不仅可以用于函数,还可以用于接口:

TypeScript
interface Box<T> {
  value: T;
  getValue(): T;
}

const stringBox: Box<string> = {
  value: 'Hello',
  getValue() {
    return this.value;
  }
};

const numberBox: Box<number> = {
  value: 42,
  getValue() {
    return this.value;
  }
};

泛型类

同样,类也可以使用泛型:

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];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }

  size(): number {
    return this.items.length;
  }
}

// 使用示例
const stringStack = new Stack<string>();
stringStack.push('Hello');
stringStack.push('World');
console.log(stringStack.pop());  // 'World'

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop());  // 3

泛型约束

有时候,我们希望泛型参数满足某些条件,这时可以使用泛型约束。

使用 extends 关键字

TypeScript
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

// 可以使用,因为 string 有 length 属性
logLength('Hello');  // 输出:5

// 可以使用,因为数组有 length 属性
logLength([1, 2, 3]);  // 输出:3

// 错误:number 没有 length 属性
// logLength(42);

在约束中使用类型参数

TypeScript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person = { name: 'Alice', age: 30, city: 'Beijing' };

const name = getProperty(person, 'name');  // 类型:string
const age = getProperty(person, 'age');    // 类型:number

// 错误:'salary' 不是 person 的属性
// const salary = getProperty(person, 'salary');

实用工具类型中的泛型

TypeScript 内置了很多实用的工具类型,它们都使用了泛型。

Partial

将类型的所有属性变为可选:

TypeScript
interface User {
  id: number;
  name: string;
  email: string;
}

// 使用 Partial
function updateUser(id: number, updates: Partial<User>): User {
  // 实现更新逻辑
  return { id, name: 'Alice', email: 'alice@example.com' };
}

// 可以只更新部分属性
updateUser(1, { name: 'Bob' });
updateUser(1, { email: 'bob@example.com' });
updateUser(1, { name: 'Charlie', email: 'charlie@example.com' });

Pick<T, K>

从类型中选择一组属性:

TypeScript
interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
  category: string;
}

// 只选择 id 和 name
type ProductSummary = Pick<Product, 'id' | 'name'>;

const summary: ProductSummary = {
  id: 1,
  name: 'Laptop'
};

Omit<T, K>

从类型中排除一组属性:

TypeScript
// 排除敏感信息
type PublicUser = Omit<User, 'password' | 'salt'>;

Record<K, T>

构造一个对象类型,其属性键为 K,属性值为 T:

TypeScript
// 创建一个映射类型
type RolePermissions = Record<'admin' | 'user' | 'guest', string[]>;

const permissions: RolePermissions = {
  admin: ['read', 'write', 'delete'],
  user: ['read', 'write'],
  guest: ['read']
};

高级泛型技巧

条件类型

条件类型允许我们根据类型条件来选择不同的类型:

TypeScript
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;   // true
type B = IsString<number>;   // false

映射类型

映射类型允许我们基于现有类型创建新类型:

TypeScript
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;
// 等价于:
// {
//   readonly name: string;
//   readonly age: number;
// }

推断类型参数

在条件类型中,可以使用 infer 关键字推断类型:

TypeScript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

function getString(): string {
  return 'hello';
}

type Result = ReturnType<typeof getString>;  // string

实际应用场景

1. API 响应处理

在前端开发中,我们经常需要处理 API 响应。使用泛型可以让我们编写类型安全的响应处理代码:

TypeScript
interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
}

interface User {
  id: number;
  name: string;
  email: string;
}

interface Product {
  id: number;
  name: string;
  price: number;
}

// 获取用户列表
async function fetchUsers(): Promise<ApiResponse<User[]>> {
  const response = await fetch('/api/users');
  return response.json();
}

// 获取产品列表
async function fetchProducts(): Promise<ApiResponse<Product[]>> {
  const response = await fetch('/api/products');
  return response.json();
}

// 使用示例
async function loadData() {
  const userResponse = await fetchUsers();
  if (userResponse.success) {
    console.log(userResponse.data[0].name);  // 类型安全
  }

  const productResponse = await fetchProducts();
  if (productResponse.success) {
    console.log(productResponse.data[0].price);  // 类型安全
  }
}

2. 通用数据存储服务

假设我们需要创建一个通用的数据存储类,可以存储任何类型的数据:

TypeScript
class DataStore<T> {
  private storage: Map<string, T> = new Map();

  set(key: string, value: T): void {
    this.storage.set(key, value);
  }

  get(key: string): T | undefined {
    return this.storage.get(key);
  }

  has(key: string): boolean {
    return this.storage.has(key);
  }

  delete(key: string): boolean {
    return this.storage.delete(key);
  }

  clear(): void {
    this.storage.clear();
  }

  size(): number {
    return this.storage.size;
  }

  forEach(callback: (value: T, key: string) => void): void {
    this.storage.forEach(callback);
  }
}

// 使用示例
const userStore = new DataStore<User>();
userStore.set('user1', { id: 1, name: 'Alice', email: 'alice@example.com' });
userStore.set('user2', { id: 2, name: 'Bob', email: 'bob@example.com' });

const user1 = userStore.get('user1');
console.log(user1?.name);  // 'Alice'

const productStore = new DataStore<Product>();
productStore.set('prod1', { id: 1, name: 'Laptop', price: 9999 });

3. 事件发射器

在构建事件驱动的应用时,泛型可以帮助我们创建类型安全的事件系统:

TypeScript
type EventHandler<T = any> = (data: T) => void;

class EventEmitter<T extends Record<string, any>> {
  private handlers: {
    [K in keyof T]?: EventHandler<T[K]>[];
  } = {};

  on<K extends keyof T>(event: K, handler: EventHandler<T[K]>): void {
    if (!this.handlers[event]) {
      this.handlers[event] = [];
    }
    this.handlers[event]!.push(handler);
  }

  off<K extends keyof T>(event: K, handler: EventHandler<T[K]>): void {
    const handlers = this.handlers[event];
    if (handlers) {
      this.handlers[event] = handlers.filter(h => h !== handler);
    }
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    const handlers = this.handlers[event];
    if (handlers) {
      handlers.forEach(handler => handler(data));
    }
  }
}

// 定义事件类型
interface AppEvents {
  userLogin: { userId: number; username: string };
  userLogout: { userId: number };
  dataUpdate: { table: string; records: number };
  error: { message: string; code: number };
}

// 使用示例
const emitter = new EventEmitter<AppEvents>();

emitter.on('userLogin', (data) => {
  console.log(`User ${data.username} logged in`);
});

emitter.on('error', (data) => {
  console.error(`Error ${data.code}: ${data.message}`);
});

// 发射事件
emitter.emit('userLogin', { userId: 1, username: 'Alice' });
emitter.emit('error', { message: 'Something went wrong', code: 500 });

4. 表单验证器

泛型可以帮助我们创建可复用的表单验证逻辑:

TypeScript
interface ValidationRule<T> {
  validate: (value: T) => boolean;
  message: string;
}

interface FormField<T> {
  value: T;
  rules: ValidationRule<T>[];
  error?: string;
}

class FormValidator<T extends Record<string, any>> {
  private fields: {
    [K in keyof T]: FormField<T[K]>;
  } = {} as any;

  addField<K extends keyof T>(
    name: K,
    initialValue: T[K],
    rules: ValidationRule<T[K]>[] = []
  ): void {
    this.fields[name] = {
      value: initialValue,
      rules
    };
  }

  validateField<K extends keyof T>(name: K): boolean {
    const field = this.fields[name];
    if (!field) return true;

    for (const rule of field.rules) {
      if (!rule.validate(field.value)) {
        field.error = rule.message;
        return false;
      }
    }

    field.error = undefined;
    return true;
  }

  validateAll(): boolean {
    let isValid = true;
    for (const name in this.fields) {
      if (!this.validateField(name as keyof T)) {
        isValid = false;
      }
    }
    return isValid;
  }

  getValues(): T {
    const values = {} as T;
    for (const name in this.fields) {
      values[name as keyof T] = this.fields[name as keyof T].value;
    }
    return values;
  }
}

// 使用示例
interface LoginForm {
  username: string;
  password: string;
  email: string;
}

const loginValidator = new FormValidator<LoginForm>();

// 添加字段和验证规则
loginValidator.addField('username', '', [
  {
    validate: (v) => v.length >= 3,
    message: '用户名至少需要 3 个字符'
  }
]);

loginValidator.addField('password', '', [
  {
    validate: (v) => v.length >= 8,
    message: '密码至少需要 8 个字符'
  },
  {
    validate: (v) => /[A-Z]/.test(v),
    message: '密码必须包含大写字母'
  }
]);

loginValidator.addField('email', '', [
  {
    validate: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
    message: '请输入有效的邮箱地址'
  }
]);

// 设置值
loginValidator.fields.username.value = 'alice';
loginValidator.fields.password.value = 'Password123';
loginValidator.fields.email.value = 'alice@example.com';

// 验证
const isValid = loginValidator.validateAll();
console.log('表单验证结果:', isValid);

最佳实践与常见陷阱

1. 选择合适的类型参数名

虽然可以使用任何名称作为类型参数,但有一些约定俗成的命名:

  • INLINE_CODE_1 - Type(最常用)
  • INLINE_CODE_2 - Key
  • INLINE_CODE_3 - Value
  • INLINE_CODE_4 - Property
  • INLINE_CODE_5 - Return type
  • INLINE_CODE_6 - Element (常用于数组或集合)

对于多个类型参数,可以使用更有描述性的名称:

TypeScript
// 好
function merge<T, U>(obj1: T, obj2: U): T & U { ... }

// 更好
function merge<TObject, TExtension>(obj1: TObject, obj2: TExtension): TObject & TExtension { ... }

2. 避免过度使用泛型

泛型很强大,但不应该在每个地方都使用。如果类型是明确的,直接使用具体类型会更清晰:

TypeScript
// 不必要
function process<T>(data: string): T {
  return data as T;
}

// 更好
function process(data: string): string {
  return data;
}

3. 提供默认类型参数

对于库作者来说,提供默认类型参数可以提高易用性:

TypeScript
interface Cache<T = any> {
  get(key: string): T | undefined;
  set(key: string, value: T): void;
}

// 使用时可以不指定类型
const cache: Cache = {
  get: (key) => undefined,
  set: (key, value) => {}
};

// 也可以指定具体类型
const userCache: Cache<User> = {
  get: (key) => undefined,
  set: (key, value) => {}
};

4. 使用泛型约束提高类型安全

当泛型参数需要满足特定条件时,一定要使用约束:

TypeScript
// 不好 - 没有约束,可能导致运行时错误
function merge<T>(obj1: T, obj2: T): T {
  return { ...obj1, ...obj2 };  // 如果 T 不是对象类型会出错
}

// 好 - 使用约束确保类型安全
function merge<T extends object>(obj1: T, obj2: T): T {
  return { ...obj1, ...obj2 };
}

总结

泛型是 TypeScript 中最强大的特性之一,它让我们能够编写更加灵活、可复用且类型安全的代码。通过本文的学习,你应该已经掌握了:

  1. 泛型的基本概念和语法
  2. 泛型函数、接口和类的使用
  3. 泛型约束和条件类型
  4. 实用工具类型的应用
  5. 实际项目中的常见应用场景
  6. 最佳实践和常见陷阱

记住,掌握泛型需要实践。建议你在日常开发中有意识地使用泛型,从简单的场景开始,逐步应用到更复杂的情况中。随着时间的推移,你会发现泛型成为你编写高质量 TypeScript 代码的得力助手。

TypeScript 的类型系统非常强大,而泛型是其中的核心。当你能够熟练运用泛型时,你就真正迈入了 TypeScript 高级开发者的行列。继续学习,继续实践,你会发现类型编程的乐趣!


关于作者:本文作者是一位热爱技术分享的开发者,专注于 TypeScript 和前端工程化领域。欢迎在评论区交流讨论!

分享到:

如果这篇文章对你有帮助,欢迎请作者喝杯咖啡 ☕

加载评论中...