Generic class
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