TypeScript 泛型(Generics)完全指南

126 阅读3分钟

TypeScript 泛型(Generics)完全指南

目录

  1. 概述
  2. 基本用法
  3. 泛型约束
  4. 泛型类
  5. 泛型接口
  6. 工具类型
  7. 最佳实践

概述

什么是泛型?

泛型是一种在定义函数、接口或类时不预先指定具体类型,而是在使用时再指定类型的特性。它可以提高代码的复用性和类型安全性。

为什么使用泛型?

  1. 代码复用
  2. 类型安全
  3. 减少冗余代码
  4. 提高灵活性

基本用法

1. 泛型函数

// 基本泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 使用方式
let output1 = identity<string>("hello");  // 显式指定类型
let output2 = identity("hello");          // 类型推断

// 泛型箭头函数
const identityArrow = <T>(arg: T): T => {
  return arg;
};

2. 多个类型参数

// 使用多个泛型类型
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

// 使用
const result = pair<string, number>("hello", 42);
const result2 = pair("hello", 42);  // 类型推断

3. 泛型数组

// 泛型数组
function reverseArray<T>(array: T[]): T[] {
  return array.reverse();
}

// 使用
const numbers = reverseArray([1, 2, 3]);
const strings = reverseArray<string>(["a", "b", "c"]);

泛型约束

1. extends 关键字

// 使用接口定义约束
interface Lengthwise {
  length: number;
}

// 约束泛型必须包含 length 属性
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);  // 现在可以安全访问 length 属性
  return arg;
}

// 使用
loggingIdentity("hello");     // 可以,字符串有 length
loggingIdentity([1, 2, 3]);   // 可以,数组有 length
loggingIdentity({ length: 10, value: 3 });  // 可以,对象包含 length

2. 多重约束

interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

// 多重约束
function printNameAndAge<T extends HasName & HasAge>(obj: T): void {
  console.log(`${obj.name} is ${obj.age} years old`);
}

// 使用
printNameAndAge({ name: "John", age: 30, occupation: "developer" });

泛型类

1. 基本泛型类

class Container<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }

  setValue(value: T): void {
    this.value = value;
  }
}

// 使用
const numberContainer = new Container<number>(123);
const stringContainer = new Container<string>("hello");

2. 泛型类与继承

class DataStorage<T extends string | number | boolean> {
  private data: T[] = [];

  addItem(item: T) {
    this.data.push(item);
  }

  removeItem(item: T) {
    const index = this.data.indexOf(item);
    if (index !== -1) {
      this.data.splice(index, 1);
    }
  }

  getItems(): T[] {
    return [...this.data];
  }
}

// 使用
const textStorage = new DataStorage<string>();
const numberStorage = new DataStorage<number>();

泛型接口

1. 基本泛型接口

interface Response<T> {
  data: T;
  status: number;
  message: string;
}

// 使用
interface User {
  id: number;
  name: string;
}

function fetchUser(): Promise<Response<User>> {
  // 实现...
  return Promise.resolve({
    data: { id: 1, name: "John" },
    status: 200,
    message: "Success"
  });
}

2. 泛型方法接口

interface Repository<T> {
  find(id: string): Promise<T>;
  save(item: T): Promise<T>;
  update(id: string, item: Partial<T>): Promise<T>;
  delete(id: string): Promise<void>;
}

// 实现
class UserRepository implements Repository<User> {
  async find(id: string): Promise<User> {
    // 实现...
    return null as any;
  }

  async save(user: User): Promise<User> {
    // 实现...
    return null as any;
  }

  async update(id: string, user: Partial<User>): Promise<User> {
    // 实现...
    return null as any;
  }

  async delete(id: string): Promise<void> {
    // 实现...
  }
}

工具类型

1. 内置工具类型

// Partial - 使所有属性可选
interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

// Record - 创建键值对类型
type PageInfo = {
  title: string;
  url: string;
}

const pages: Record<string, PageInfo> = {
  home: { title: "Home", url: "/" },
  about: { title: "About", url: "/about" }
};

2. 自定义工具类型

// 移除只读属性
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

// 深度 Partial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// 类型过滤
type FilterString<T> = {
  [P in keyof T]: T[P] extends string ? P : never;
}[keyof T];

最佳实践

1. 命名约定

// 常用泛型参数命名
T - Type
K - Key
V - Value
E - Element
P - Property
R - Return Type
S, U, V etc. - 其他类型

2. 默认类型

// 提供默认类型参数
interface ApiResponse<T = any> {
  data: T;
  status: number;
}

// 使用
const response: ApiResponse = { data: "hello", status: 200 };
const typedResponse: ApiResponse<number> = { data: 42, status: 200 };

3. 条件类型

// 根据条件选择类型
type TypeName<T> = 
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

// 使用
type T0 = TypeName<string>;  // "string"
type T1 = TypeName<number>;  // "number"

实际应用示例

1. React 组件

interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: Props<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// 使用
<List<string>
  items={["a", "b", "c"]}
  renderItem={(item) => <span>{item}</span>}
/>

2. 状态管理

class State<T> {
  private listeners: ((state: T) => void)[] = [];
  private state: T;

  constructor(initialState: T) {
    this.state = initialState;
  }

  getState(): T {
    return this.state;
  }

  setState(newState: T): void {
    this.state = newState;
    this.notify();
  }

  subscribe(listener: (state: T) => void): () => void {
    this.listeners.push(listener);
    return () => {
      this.listeners = this.listeners.filter(l => l !== listener);
    };
  }

  private notify(): void {
    this.listeners.forEach(listener => listener(this.state));
  }
}

总结

  1. 泛型的优点:

    • 类型安全
    • 代码复用
    • 灵活性
    • 可维护性
  2. 使用场景:

    • 通用组件
    • 数据容器
    • API 请求
    • 状态管理
  3. 最佳实践:

    • 合理使用约束
    • 提供默认类型
    • 遵循命名约定
    • 保持简单清晰