2.TypeScript 泛型实战指南:打造灵活高效的类型安全代码

小鱼
2025-08-13 22:01:27
TypeScript

在 TypeScript 开发中,你是否经常遇到这样的困境:相似的逻辑需要为不同类型重复编写代码?或是使用 any 类型后丢失了类型安全?泛型正是解决这些痛点的利器!本文将带你深入掌握泛型的应用技巧。

一、泛型:类型系统的函数参数

想象一下,你要写一个函数,既能处理数字也能处理字符串。没有泛型前,你可能会这样写:

typescript

// 处理数字
function processNumber(num: number): number {
  return num * 2;
}

// 处理字符串
function processString(str: string): string {
  return str.toUpperCase();
}

这种重复代码既冗余又难以维护。泛型 的出现让代码可以像函数接收参数一样接收类型:

typescript

// 泛型解决方案
function processValue<T>(value: T): T {
  // 处理逻辑...
  return value;
}

泛型核心概念

  • 类型变量<T> 中的 T 是类型变量,类似函数参数
  • 类型参数化:调用时指定具体类型,如 processValue<number>(10)
  • 类型保留:输入类型和输出类型保持一致

二、泛型函数:编写灵活的工具函数

基础应用:创建重复元素的数组

typescript

function createRepeatArray<T>(value: T, count: number): T[] {
  return Array(count).fill(value);
}

// 使用
const numbers = createRepeatArray(5, 3); // 推断为 number[]
// [5, 5, 5]

const strings = createRepeatArray('hello', 2); // 推断为 string[]
// ['hello', 'hello']

多个类型参数:处理不同类型组合

typescript

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

// 使用
const userRole = createPair('Alice', 'Admin'); // [string, string]
const userAge = createPair('Bob', 30); // [string, number]

三、泛型接口:构建通用数据模型

API 响应通用结构

typescript

// 定义通用API响应接口
interface ApiResponse<T> {
  success: boolean;
  code: number;
  data: T;
  message?: string;
}

// 用户数据接口
interface User {
  id: number;
  name: string;
  email: string;
}

// 使用
const userResponse: ApiResponse<User> = {
  success: true,
  code: 200,
  data: {
    id: 1,
    name: 'Alice',
    email: 'alice@example.com'
  }
};

// 产品数据接口
interface Product {
  id: number;
  name: string;
  price: number;
}

const productResponse: ApiResponse<Product> = {
  success: true,
  code: 200,
  data: {
    id: 101,
    name: 'Laptop',
    price: 1299
  }
};

CRUD 操作通用接口

typescript

// 通用CRUD接口
interface Repository<T> {
  getAll(): T[];
  getById(id: number): T | undefined;
  create(item: T): void;
  update(id: number, item: Partial<T>): boolean;
  delete(id: number): boolean;
}

// 用户实体实现
class UserRepository implements Repository<User> {
  private users: User[] = [];
  
  getAll(): User[] {
    return [...this.users];
  }
  
  getById(id: number): User | undefined {
    return this.users.find(user => user.id === id);
  }
  
  create(user: User): void {
    this.users.push(user);
  }
  
  update(id: number, updates: Partial<User>): boolean {
    const user = this.getById(id);
    if (!user) return false;
  
    Object.assign(user, updates);
    return true;
  }
  
  delete(id: number): boolean {
    const index = this.users.findIndex(user => user.id === id);
    if (index === -1) return false;
  
    this.users.splice(index, 1);
    return true;
  }
}

四、泛型类:创建灵活的数据容器

通用缓存系统

typescript

class Cache<T> {
  private data = new Map<string, T>();
  private expiration = new Map<string, number>();
  
  set(key: string, value: T, ttl: number = 30000): void {
    this.data.set(key, value);
    this.expiration.set(key, Date.now() + ttl);
  
    // 设置自动过期
    setTimeout(() => {
      if (this.expiration.get(key)! <= Date.now()) {
        this.delete(key);
      }
    }, ttl);
  }
  
  get(key: string): T | undefined {
    if (!this.data.has(key)) return undefined;
  
    const expiry = this.expiration.get(key)!;
    if (expiry <= Date.now()) {
      this.delete(key);
      return undefined;
    }
  
    return this.data.get(key);
  }
  
  delete(key: string): boolean {
    this.expiration.delete(key);
    return this.data.delete(key);
  }
}

// 使用
const userCache = new Cache<User>();
userCache.set('user-1', { id: 1, name: 'Alice' });

const configCache = new Cache<{ theme: string }>();
configCache.set('preferences', { theme: 'dark' });

通用数据包装器

typescript

class Wrapper<T> {
  constructor(private value: T) {}
  
  get(): T {
    return this.value;
  }
  
  set(newValue: T): void {
    this.value = newValue;
  }
  
  map<U>(fn: (value: T) => U): Wrapper<U> {
    return new Wrapper(fn(this.value));
  }
}

// 使用
const numberWrapper = new Wrapper(10);
const doubled = numberWrapper.map(n => n * 2); // Wrapper<number>

const stringWrapper = new Wrapper('hello');
const upperCased = stringWrapper.map(s => s.toUpperCase()); // Wrapper<string>

五、泛型约束:精确控制类型范围

基础约束:确保属性存在

typescript

// 确保类型有length属性
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(item: T): void {
  console.log(`长度:${item.length}`);
}

logLength('hello'); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
// logLength(123); // 错误:数字没有length属性

高级约束:keyof 和对象属性

typescript

// 获取对象属性值
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'Bob', age: 30 };
console.log(getProperty(user, 'name')); // 'Bob'
// getProperty(user, 'email'); // 错误:email不在user的属性中

// 动态创建对象
function createObject<T>(keys: (keyof T)[], values: any[]): T {
  return keys.reduce((obj, key, index) => {
    obj[key] = values[index];
    return obj;
  }, {} as T);
}

const newUser = createObject<User>(['id', 'name'], [2, 'Charlie']);
// { id: 2, name: 'Charlie' }

六、实际应用场景与最佳实践

1. API 客户端封装

typescript

class ApiClient {
  async get<T>(url: string): Promise<T> {
    const response = await fetch(url);
    return response.json();
  }
  
  async post<T, U>(url: string, body: T): Promise<U> {
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body)
    });
    return response.json();
  }
}

// 使用
const api = new ApiClient();
const user = await api.get<User>('/api/users/1');
const newProduct = await api.post<Product, ApiResponse<Product>>(
  '/api/products',
  { name: 'Tablet', price: 499 }
);

2. 表单验证器

typescript

interface ValidationRule<T> {
  value: T;
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  min?: number;
  max?: number;
}

function validate<T>(rule: ValidationRule<T>): string[] {
  const errors: string[] = [];
  
  if (rule.required) {
    if (!rule.value) errors.push('字段为必填项');
  }
  
  if (typeof rule.value === 'string') {
    if (rule.minLength && rule.value.length < rule.minLength) {
      errors.push(`长度不能少于 ${rule.minLength} 个字符`);
    }
  
    if (rule.maxLength && rule.value.length > rule.maxLength) {
      errors.push(`长度不能超过 ${rule.maxLength} 个字符`);
    }
  }
  
  if (typeof rule.value === 'number') {
    if (rule.min && rule.value < rule.min) {
      errors.push(`值不能小于 ${rule.min}`);
    }
  
    if (rule.max && rule.value > rule.max) {
      errors.push(`值不能大于 ${rule.max}`);
    }
  }
  
  return errors;
}

// 使用
const usernameErrors = validate<string>({
  value: 'al',
  required: true,
  minLength: 3
});
// ['长度不能少于 3 个字符']

const ageErrors = validate<number>({
  value: 15,
  min: 18
});
// ['值不能小于 18']

七、泛型开发最佳实践

  1. 命名规范

    • 使用有意义的类型变量名(不只是 T)
    • 推荐:TDataTResponseTEntity
  2. 优先类型推断typescript

    // 好的写法:让TS自动推断
    const numbers = createRepeatArray(5, 3);
    
    // 不必要的写法:显式指定类型
    const numbers = createRepeatArray<number>(5, 3);
    
  3. 合理使用约束

    • 避免过度约束限制灵活性
    • 只添加必要的约束条件
  4. 结合类型别名typescript

    // 复杂泛型使用类型别名
    type ApiResult<T> = {
      success: boolean;
      data: T;
      error?: string;
    };
    
    function handleResponse<T>(response: ApiResult<T>) {
      // ...
    }
    
  5. 避免泛型过度使用

    • 简单场景不需要泛型
    • 优先考虑函数重载

    typescript

    // 函数重载替代简单泛型
    function process(value: string): string;
    function process(value: number): number;
    function process(value: any): any {
      // 实现...
    }
    
Copyright © 2025 aipanzhou.com All Rights Reserved.
备案号:黔ICP备2023000741号-1
贵公网安备 52022202000096号