阿里云主机折上折
  • 微信号
Current Site:Index > Generic class

Generic class

Author:Chuan Chen 阅读数:17021人阅读 分类: TypeScript

TypeScript's generic classes allow developers to create reusable components that can handle multiple data types without sacrificing type safety. Generic classes introduce flexibility through type parameters at the time of class definition, enabling the same class to adapt to different type requirements.

Basic Syntax of Generic Classes

Generic classes declare type parameters using angle brackets <T> after the class name, where T is a placeholder for a type variable that will be replaced by a concrete type during usage. For example:

class Box<T> {
  private content: T;

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

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

const numberBox = new Box<number>(42);
const stringBox = new Box<string>("Hello");

Here, the Box class declares a generic parameter <T>, and the types of the content property and getValue method depend on T. During instantiation, number and string types are specified, allowing type checking to work effectively for different scenarios.

Generic Constraints and Default Types

Generic classes can use extends to add constraints, limiting the range of type parameters. They also support providing default types for generic parameters:

interface HasLength {
  length: number;
}

class Container<T extends HasLength = string> {
  constructor(private data: T) {}

  logLength(): void {
    console.log(this.data.length);
  }
}

const strContainer = new Container("abc"); // Uses default type string
const arrContainer = new Container<number[]>([1, 2, 3]); // Explicitly specifies type

In this example, T must satisfy the HasLength interface, with a default type of string. Attempting to use a type without a length property will result in a compilation error.

Multiple Type Parameters

Generic classes support multiple type parameters, which is useful for scenarios involving multiple related types:

class Pair<K, V> {
  constructor(
    public key: K,
    public value: V
  ) {}

  swap(): Pair<V, K> {
    return new Pair(this.value, this.key);
  }
}

const nameAge = new Pair<string, number>("Alice", 30);
const swapped = nameAge.swap(); // Type becomes Pair<number, string>

The Pair class defines two generic parameters <K, V>, and the swap method swaps their positions while maintaining type system integrity.

Limitations on Static Members

Note that static members of a generic class cannot reference the class's type parameters:

class GenericStatic<T> {
  static defaultValue: T; // Error: Static members cannot reference type parameters
  instanceValue: T; // Correct
}

This is because static members belong to the class itself, not instances, while generic parameters are only resolved during instantiation.

Combining with Generic Interfaces

Generic classes can implement generic interfaces, creating a more flexible type system:

interface IRepository<T> {
  getById(id: string): T;
  save(entity: T): void;
}

class UserRepository implements IRepository<User> {
  getById(id: string): User {
    // Implementation logic
  }
  save(user: User): void {
    // Implementation logic
  }
}

Complex Type Inference Example

Generic classes can automatically infer type relationships in complex scenarios:

class TreeNode<T> {
  constructor(
    public value: T,
    public left: TreeNode<T> | null = null,
    public right: TreeNode<T> | null = null
  ) {}
}

const tree = new TreeNode(1, new TreeNode(2), new TreeNode(3));
// Automatically inferred as TreeNode<number>

Runtime Behavior of Type Erasure

Although TypeScript performs type checking at compile time, generic type information is erased at runtime:

class Erasure<T> {
  constructor(private data: T) {}
}

const instance1 = new Erasure<string>("test");
const instance2 = new Erasure<number>(123);

console.log(instance1 instanceof Erasure); // true
console.log(instance2 instanceof Erasure); // true
// Runtime cannot distinguish between Erasure<string> and Erasure<number>

Advanced Pattern: Recursive Generics

Generic classes can define recursive type structures, suitable for self-referential data models:

class NestedArray<T> {
  constructor(public items: Array<T | NestedArray<T>>) {}

  flatten(): T[] {
    return this.items.reduce((acc: T[], item) => {
      return acc.concat(item instanceof NestedArray ? item.flatten() : item);
    }, []);
  }
}

const nested = new NestedArray([1, [new NestedArray([2, 3]), 4]);
console.log(nested.flatten()); // [1, 2, 3, 4]

Combining Factory Functions with Generic Classes

Factory functions can create instances of generic classes while preserving full type information:

function createComponent<T>(ctor: new () => T): T {
  return new ctor();
}

class Button {
  click() {
    console.log("Clicked!");
  }
}

const button = createComponent(Button); // Type inferred as Button
button.click();

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:泛型接口

下一篇:泛型约束

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.