TS泛型笔记

发布于:2025-06-28 ⋅ 阅读:(19) ⋅ 点赞:(0)
1. 泛型基础概念

定义:泛型是 TypeScript 中允许创建可复用组件的特性,这些组件可以支持多种数据类型,而非单一特定类型。

核心优势

  • 代码复用性:同一组件可处理不同类型数据
  • 类型安全:在编译阶段捕获类型错误
  • 灵活性:保持代码的灵活性同时提供强类型支持

泛型函数

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

// 使用方式
const result = identity<string>("hello"); // 指定类型参数
const inferredResult = identity(123); // 类型自动推断为 number

泛型类

class Box<T> {
  private value: T;
  
  constructor(value: T) {
    this.value = value;
  }
  
  getValue(): T {
    return this.value;
  }
}

// 使用示例
const numberBox = new Box<number>(42);
const stringBox = new Box("text"); // 类型自动推断
2. 泛型约束详解

基本约束

interface HasLength {
  length: number;
}

// 约束 T 必须实现 HasLength 接口
function getLength<T extends HasLength>(arg: T): number {
  return arg.length;
}

// 有效调用
getLength("hello"); // 字符串有 length 属性
getLength([1, 2, 3]); // 数组有 length 属性

// 错误调用
getLength(123); // 数字没有 length 属性

键约束

// 约束 K 必须是 T 类型的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // 正确
const invalid = getProperty(user, "address"); // 错误:address 不是 user 的键

构造函数约束

interface Constructor<T> {
  new (...args: any[]): T;
}

function createInstance<T>(ctor: Constructor<T>): T {
  return new ctor();
}

class MyClass {
  constructor() {}
}

const instance = createInstance(MyClass); // 创建 MyClass 实例
3. 接口深入理解

接口定义

interface Person {
  name: string;
  age?: number; // 可选属性
  readonly id: number; // 只读属性
  [propName: string]: any; // 任意属性
}

// 使用接口
const person: Person = {
  name: "Bob",
  id: 12345,
  address: "123 Street" // 任意属性
};

person.age = 30; // 可选属性可赋值
person.id = 67890; // 错误:只读属性不可修改

接口继承

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

// 实现接口
const square: Square = {
  color: "blue",
  sideLength: 10
};

函数类型接口

interface SearchFunc {
  (source: string, subString: string): boolean;
}

const mySearch: SearchFunc = function(src, sub) {
  return src.includes(sub);
};
4. 泛型与接口结合应用

泛型接口

interface Container<T> {
  value: T;
  getValue: () => T;
  setValue: (newValue: T) => void;
}

// 实现泛型接口
class BoxContainer<T> implements Container<T> {
  constructor(private _value: T) {}
  
  getValue() {
    return this._value;
  }
  
  setValue(newValue: T) {
    this._value = newValue;
  }
}

泛型工具类型

// Partial<T> - 创建所有属性可选的类型
interface User {
  name: string;
  age: number;
}

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

// Readonly<T> - 创建所有属性只读的类型
type ReadonlyUser = Readonly<User>;

// Pick<T, K> - 从 T 中选择一组属性 K
type NameOnly = Pick<User, "name">; 
// 等价于 { name: string; }

// Record<K, T> - 创建一个 K 类型为键,T 类型为值的对象类型
type UserRecords = Record<string, User>;
5. 高级泛型技术

条件类型

// 基本条件类型语法
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 StringArray = ToArray<string>; // string[]
type NumberOrStringArray = ToArray<number | string>; // number[] | string[]

映射类型

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

type Optional<T> = {
  [P in keyof T]?: T[P];
};

// 使用示例
type ReadonlyUser = Readonly<User>;
type OptionalUser = Optional<User>;

类型推断与 infer

// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type Num = ReturnType<() => number>; // number
type Str = ReturnType<(s: string) => string>; // string

案例 1:实现一个通用的响应接口

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

// 使用示例
type UserResponse = ApiResponse<User>;
type ProductListResponse = ApiResponse<Product[]>;

// 处理 API 响应
function processResponse<T>(response: ApiResponse<T>): T {
  if (response.code === 200) {
    return response.data;
  }
  throw new Error(response.message);
}

案例 2:实现一个泛型缓存类

class Cache<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);
  }
  
  clear(): void {
    this.storage.clear();
  }
}

// 使用示例
const userCache = new Cache<User>();
userCache.set("currentUser", { name: "Alice", age: 30 });
const currentUser = userCache.get("currentUser");

案例总结

  1. 明确类型参数:除非类型可以被可靠推断,否则明确指定泛型类型参数
  2. 约束类型参数:使用泛型约束确保类型参数满足特定条件
  3. 避免过度泛型:仅在必要时使用泛型,避免增加不必要的复杂性
  4. 优先使用接口而非类型别名:对于对象类型定义,优先使用 interface 而非 type
  5. 使用工具类型:利用 TypeScript 内置的工具类型(Partial、Readonly 等)简化常见操作
常见错误与解决方案

错误 1:泛型函数返回类型不匹配

// 错误示例
function createArray<T>(length: number, value: T): number[] {
  return Array(length).fill(value); // 错误:可能返回非 number[] 类型
}

// 解决方案
function createArray<T>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

错误 2:接口属性冲突

// 错误示例
interface A { x: number; }
interface B { x: string; }
interface C extends A, B {} // 错误:属性 x 类型冲突

// 解决方案
interface C { x: number | string; } // 联合类型解决冲突

错误 3:泛型约束不足

// 错误示例
function getLength<T>(arg: T): number {
  return arg.length; // 错误:T 不一定有 length 属性
}

// 解决方案
function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}
与其他 TypeScript 特性的组合

泛型与装饰器

function logClass<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    constructor(...args: any[]) {
      console.log("Creating instance with arguments:", args);
      super(...args);
    }
  };
}

@logClass
class MyClass {
  constructor(public name: string) {}
}

const instance = new MyClass("Test"); // 输出: Creating instance with arguments: ["Test"]

泛型与命名空间

namespace Utils {
  export function identity<T>(arg: T): T {
    return arg;
  }
}

const result = Utils.identity("hello"); // 使用命名空间中的泛型函数

总结

泛型和接口是 TypeScript 中最强大的两个特性,它们共同构成了 TypeScript 类型系统的核心。通过泛型,我们可以创建高度可复用的组件,同时保持类型安全;通过接口,我们可以定义清晰的契约,规范代码结构。合理运用这两个特性,可以显著提升代码质量、可维护性和开发效率。

在实际开发中,建议遵循最佳实践,从简单的泛型应用开始,逐步深入到高级技术。同时,注意避免常见错误,充分利用 TypeScript 提供的工具类型和内置功能,打造健壮、灵活的 TypeScript 应用。


网站公告

今日签到

点亮在社区的每一天
去签到