Skip to content

TypeScript泛型编程

泛型是TypeScript中最强大的特性之一,它允许我们创建可重用的组件,这些组件可以与多种类型一起工作,而不仅仅是单一类型。本文将深入探讨TypeScript泛型的概念、语法和高级应用。

泛型基础

为什么需要泛型?

在没有泛型的情况下,我们通常需要为不同类型创建多个几乎相同的函数:

typescript
function identityString(arg: string): string {
  return arg;
}

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

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

或者使用any类型,但这会丢失类型信息:

typescript
function identity(arg: any): any {
  return arg;
}

// 使用any后,我们失去了类型检查
const num = identity(42); // num的类型是any,而不是number

泛型解决了这个问题,它允许我们保留类型信息:

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

// 显式指定类型参数
const str = identity<string>("hello"); // str的类型是string

// 让TypeScript推断类型参数
const num = identity(42); // num的类型是number

泛型语法

泛型使用尖括号<>语法,其中包含类型参数:

typescript
// 泛型函数
function example<T>(arg: T): T {
  return arg;
}

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

// 泛型接口
interface Box<T> {
  value: T;
}

// 泛型类
class Container<T> {
  private item: T;
  
  constructor(item: T) {
    this.item = item;
  }
  
  getItem(): T {
    return this.item;
  }
}

// 泛型类型别名
type Result<T> = {
  data: T;
  error: string | null;
};

使用泛型

泛型可以在多种场景中使用:

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

const strResult = identity("hello");
const numResult = identity(42);
const boolResult = identity(true);

// 泛型接口
interface Pair<T, U> {
  first: T;
  second: U;
}

const numberStringPair: Pair<number, string> = { first: 1, second: "one" };
const booleanDatePair: Pair<boolean, Date> = { first: true, second: new Date() };

// 泛型类
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;
  }
}

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

const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop()); // "world"

泛型约束

基本约束

有时我们需要限制泛型类型必须具有特定的属性或方法。这可以通过extends关键字实现:

typescript
// 定义一个接口作为约束
interface Lengthwise {
  length: number;
}

// 限制T必须有length属性
function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // 现在可以安全访问length属性
  return arg;
}

logLength("hello"); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10, value: 42 }); // 10

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

使用类型参数约束另一个类型参数

一个类型参数可以被另一个类型参数约束:

typescript
// 确保key是obj的属性
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person = { name: "Alice", age: 30 };

console.log(getProperty(person, "name")); // "Alice"
console.log(getProperty(person, "age")); // 30

// 错误:Argument of type '"location"' is not assignable to parameter of type '"name" | "age"'
// getProperty(person, "location");

泛型约束中使用类型参数

我们可以声明一个类型参数,该参数被另一个类型参数约束:

typescript
function createInstance<T, U extends new () => T>(Constructor: U): T {
  return new Constructor();
}

class Animal {
  numLegs: number = 4;
}

const animal = createInstance(Animal);
console.log(animal.numLegs); // 4

默认类型参数

TypeScript允许为泛型类型参数指定默认值:

typescript
// 带默认值的泛型接口
interface ApiResponse<T = any> {
  data: T;
  status: number;
  message: string;
}

// 不指定类型参数时使用默认值
const response: ApiResponse = {
  data: { id: 1, name: "Product" },
  status: 200,
  message: "Success"
};

// 指定类型参数
interface User {
  id: number;
  name: string;
}

const userResponse: ApiResponse<User> = {
  data: { id: 1, name: "Alice" },
  status: 200,
  message: "User found"
};

高级泛型模式

泛型工厂

泛型工厂是一种创建特定类型实例的函数:

typescript
// 泛型工厂函数
function createFactory<T>(initialValue: T): {
  get: () => T;
  set: (value: T) => void;
} {
  let value = initialValue;
  
  return {
    get: () => value,
    set: (newValue: T) => { value = newValue; }
  };
}

// 字符串工厂
const stringFactory = createFactory("hello");
console.log(stringFactory.get()); // "hello"
stringFactory.set("world");
console.log(stringFactory.get()); // "world"

// 数字工厂
const numberFactory = createFactory(42);
console.log(numberFactory.get()); // 42
numberFactory.set(100);
console.log(numberFactory.get()); // 100

// 对象工厂
interface User {
  name: string;
  age: number;
}

const userFactory = createFactory<User>({ name: "Alice", age: 30 });
console.log(userFactory.get()); // { name: "Alice", age: 30 }
userFactory.set({ name: "Bob", age: 25 });
console.log(userFactory.get()); // { name: "Bob", age: 25 }

条件类型

条件类型允许我们基于类型关系创建新的类型:

typescript
// 基本条件类型
type IsString<T> = T extends string ? true : false;

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

// 分配条件类型
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumberArray = ToArray<string | number>; // string[] | number[]

// 条件类型中的推断
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function greeting(): string {
  return "Hello";
}

type GreetingReturnType = ReturnType<typeof greeting>; // string

// 高级条件类型
type NonNullable<T> = T extends null | undefined ? never : T;
type StringOrNumber = string | number | null | undefined;
type NonNullStringOrNumber = NonNullable<StringOrNumber>; // string | number

映射类型

映射类型允许我们基于旧类型创建新类型,通过转换每个属性:

typescript
// 基本映射类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

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

type ReadonlyUser = Readonly<User>;
// 等同于:{ readonly name: string; readonly age: number; }

// 可选映射类型
type Partial<T> = {
  [P in keyof T]?: T[P];
};

type PartialUser = Partial<User>;
// 等同于:{ name?: string; age?: number; }

// 记录映射类型
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

type UserRoles = Record<"admin" | "user" | "guest", { permissions: string[] }>;
// 等同于:{ admin: { permissions: string[] }; user: { permissions: string[] }; guest: { permissions: string[] }; }

// 高级映射类型
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type NameOnly = Pick<User, "name">;
// 等同于:{ name: string; }

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

type WithoutAge = Omit<User, "age">;
// 等同于:{ name: string; }

泛型递归类型

递归类型是指在其定义中引用自身的类型:

typescript
// 递归类型:JSON值
type JSONValue = 
  | string 
  | number 
  | boolean 
  | null 
  | JSONValue[] 
  | { [key: string]: JSONValue };

const json: JSONValue = {
  name: "Alice",
  age: 30,
  isAdmin: false,
  address: {
    street: "123 Main St",
    city: "Anytown"
  },
  hobbies: ["reading", "swimming"]
};

// 递归类型:嵌套对象
type NestedObject<T> = {
  value: T;
  children?: NestedObject<T>[];
};

const nested: NestedObject<string> = {
  value: "root",
  children: [
    { value: "child1" },
    { 
      value: "child2",
      children: [
        { value: "grandchild" }
      ]
    }
  ]
};

// 递归类型:深度部分
type DeepPartial<T> = T extends object ? {
  [P in keyof T]?: DeepPartial<T[P]>;
} : T;

interface Department {
  name: string;
  employees: {
    id: number;
    name: string;
    address: {
      street: string;
      city: string;
      zipCode: string;
    }
  }[];
}

// 所有属性都是可选的,包括嵌套属性
const partialDept: DeepPartial<Department> = {
  name: "Engineering",
  employees: [
    {
      id: 1,
      address: {
        city: "San Francisco"
        // street和zipCode是可选的
      }
    }
  ]
};

泛型最佳实践

命名约定

泛型类型参数通常使用单个大写字母或有描述性的名称:

typescript
// 单个字母(常见约定)
function identity<T>(arg: T): T {
  return arg;
}

// 描述性名称(更清晰)
function createPair<First, Second>(first: First, second: Second): [First, Second] {
  return [first, second];
}

// 常见泛型命名约定
// T: 类型参数的默认名称
// K: 键类型,通常是对象的键
// V: 值类型,通常与K一起使用
// E: 元素类型,用于数组或集合
// P: 属性类型
// R: 返回类型
// S, U, V等: 额外的类型参数

// 示例
function get<O extends object, K extends keyof O>(obj: O, key: K): O[K] {
  return obj[key];
}

function map<T, U>(array: T[], fn: (item: T) => U): U[] {
  return array.map(fn);
}

何时使用泛型

泛型最适合以下场景:

  1. 当函数或类需要处理多种类型时
  2. 当你需要保留类型信息时
  3. 当你需要在多个地方使用相同的类型时
typescript
// 适合使用泛型:处理多种类型并保留类型信息
function first<T>(array: T[]): T | undefined {
  return array[0];
}

// 适合使用泛型:类型安全的数据容器
class Result<T, E = Error> {
  private constructor(
    private readonly value?: T,
    private readonly error?: E
  ) {}
  
  static success<T>(value: T): Result<T, never> {
    return new Result(value, undefined);
  }
  
  static failure<E>(error: E): Result<never, E> {
    return new Result(undefined, error);
  }
  
  isSuccess(): boolean {
    return this.error === undefined;
  }
  
  isFailure(): boolean {
    return this.error !== undefined;
  }
  
  getOrThrow(): T {
    if (this.isSuccess()) {
      return this.value as T;
    }
    throw this.error;
  }
  
  getOrElse(defaultValue: T): T {
    return this.isSuccess() ? (this.value as T) : defaultValue;
  }
}

// 使用示例
function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return Result.failure("Division by zero");
  }
  return Result.success(a / b);
}

const result = divide(10, 2);
if (result.isSuccess()) {
  console.log(result.getOrThrow()); // 5
} else {
  console.error("Error occurred");
}

const errorResult = divide(10, 0);
console.log(errorResult.getOrElse(0)); // 0

避免过度使用泛型

虽然泛型很强大,但过度使用会使代码难以理解:

typescript
// 过度使用泛型的例子
function processData<T, U, V, W, X>(
  data: T,
  transformer: (input: T) => U,
  validator: (input: U) => V extends boolean ? W : X
): V extends boolean ? W : X {
  const transformed = transformer(data);
  return validator(transformed) as any;
}

// 更好的方式:拆分成更小、更专注的函数
function transform<T, U>(data: T, transformer: (input: T) => U): U {
  return transformer(data);
}

function validate<T>(data: T, validator: (input: T) => boolean): boolean {
  return validator(data);
}

function processData<T, U>(
  data: T,
  transformer: (input: T) => U,
  validator: (input: U) => boolean
): U | null {
  const transformed = transformer(data);
  return validator(transformed) ? transformed : null;
}

实际应用示例

类型安全的API客户端

typescript
// 类型安全的API客户端
interface ApiClient {
  get<T>(url: string): Promise<T>;
  post<T, U>(url: string, data: T): Promise<U>;
  put<T, U>(url: string, data: T): Promise<U>;
  delete<T>(url: string): Promise<T>;
}

class HttpClient implements ApiClient {
  private baseUrl: string;
  
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }
  
  async get<T>(url: string): Promise<T> {
    const response = await fetch(`${this.baseUrl}${url}`);
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }
    return response.json();
  }
  
  async post<T, U>(url: string, data: T): Promise<U> {
    const response = await fetch(`${this.baseUrl}${url}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }
    return response.json();
  }
  
  async put<T, U>(url: string, data: T): Promise<U> {
    const response = await fetch(`${this.baseUrl}${url}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }
    return response.json();
  }
  
  async delete<T>(url: string): Promise<T> {
    const response = await fetch(`${this.baseUrl}${url}`, {
      method: 'DELETE'
    });
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }
    return response.json();
  }
}

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

interface CreateUserDto {
  name: string;
  email: string;
  password: string;
}

const apiClient = new HttpClient('https://api.example.com');

// 获取用户列表
async function getUsers(): Promise<User[]> {
  return apiClient.get<User[]>('/users');
}

// 创建新用户
async function createUser(user: CreateUserDto): Promise<User> {
  return apiClient.post<CreateUserDto, User>('/users', user);
}

// 更新用户
async function updateUser(id: number, user: Partial<User>): Promise<User> {
  return apiClient.put<Partial<User>, User>(`/users/${id}`, user);
}

// 删除用户
async function deleteUser(id: number): Promise<void> {
  return apiClient.delete<void>(`/users/${id}`);
}

状态管理

typescript
// 类型安全的状态管理
type Listener<T> = (state: T) => void;

class Store<T> {
  private state: T;
  private listeners: Listener<T>[] = [];
  
  constructor(initialState: T) {
    this.state = initialState;
  }
  
  getState(): T {
    return this.state;
  }
  
  setState(newState: Partial<T>): void {
    this.state = { ...this.state, ...newState };
    this.notify();
  }
  
  subscribe(listener: Listener<T>): () => void {
    this.listeners.push(listener);
    
    // 返回取消订阅函数
    return () => {
      const index = this.listeners.indexOf(listener);
      if (index > -1) {
        this.listeners.splice(index, 1);
      }
    };
  }
  
  private notify(): void {
    this.listeners.forEach(listener => listener(this.state));
  }
}

// 使用示例
interface AppState {
  user: {
    name: string;
    isLoggedIn: boolean;
  };
  theme: 'light' | 'dark';
  notifications: string[];
}

const initialState: AppState = {
  user: {
    name: '',
    isLoggedIn: false
  },
  theme: 'light',
  notifications: []
};

const store = new Store<AppState>(initialState);

// 订阅状态变化
const unsubscribe = store.subscribe(state => {
  console.log('State changed:', state);
});

// 更新状态
store.setState({
  user: {
    name: 'Alice',
    isLoggedIn: true
  }
});

// 更新主题
store.setState({ theme: 'dark' });

// 添加通知
store.setState({
  notifications: [...store.getState().notifications, 'New message']
});

// 取消订阅
unsubscribe();

泛型组件(React示例)

tsx
// 泛型React组件
import React, { useState } from 'react';

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
}

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

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

function UserList() {
  const users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { id: 2, name: 'Bob', email: 'bob@example.com' },
    { id: 3, name: 'Charlie', email: 'charlie@example.com' }
  ];
  
  return (
    <List<User>
      items={users}
      renderItem={user => (
        <div>
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      )}
      keyExtractor={user => user.id}
    />
  );
}

// 泛型钩子
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
  
  const setValue = (value: T) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}

// 使用泛型钩子
function UserSettings() {
  interface Settings {
    theme: 'light' | 'dark';
    fontSize: number;
    notifications: boolean;
  }
  
  const [settings, setSettings] = useLocalStorage<Settings>('user-settings', {
    theme: 'light',
    fontSize: 16,
    notifications: true
  });
  
  return (
    <div>
      <h2>User Settings</h2>
      <label>
        Theme:
        <select
          value={settings.theme}
          onChange={e => setSettings({ ...settings, theme: e.target.value as 'light' | 'dark' })}
        >
          <option value="light">Light</option>
          <option value="dark">Dark</option>
        </select>
      </label>
      <label>
        Font Size:
        <input
          type="number"
          value={settings.fontSize}
          onChange={e => setSettings({ ...settings, fontSize: parseInt(e.target.value) })}
        />
      </label>
      <label>
        Notifications:
        <input
          type="checkbox"
          checked={settings.notifications}
          onChange={e => setSettings({ ...settings, notifications: e.target.checked })}
        />
      </label>
    </div>
  );
}

总结

泛型是TypeScript中最强大的特性之一,它允许我们编写灵活、可重用且类型安全的代码。通过掌握泛型的基础知识、约束、条件类型和映射类型,你可以创建适应各种场景的通用组件。

记住,泛型的目标是提高代码的可重用性,同时保持类型安全。在实际应用中,合理使用泛型可以显著提高代码质量和开发效率。随着你对泛型的深入理解,你将能够解决越来越复杂的类型问题,并创建更加健壮的应用程序。

贡献者

huoshan
huoshan

用知识点燃技术的火山