在 TypeScript 中,类 和 泛型 是核心特性之一,结合起来使用,可以显著增强代码的复用性和类型安全性。以下是 TypeScript 中类与泛型基本的使用方法和场景
1. 泛型基础
泛型(Generics)允许我们定义 类型参数,可以在类、接口或函数中灵活地适配不同的类型。
示例:简单泛型类
class Box<T> {
private content: T;
constructor(content: T) {
this.content = content;
}
getContent(): T {
return this.content;
}
setContent(content: T): void {
this.content = content;
}
}
// 使用
const stringBox = new Box<string>("Hello, TypeScript");
console.log(stringBox.getContent()); // 输出: "Hello, TypeScript"
const numberBox = new Box<number>(123);
console.log(numberBox.getContent()); // 输出: 123
说明
T
是一个占位符,代表类型参数,用户在实例化时决定其具体类型。- 使用泛型让
Box
可以适配不同类型,不需要为每种类型单独定义类。
2. 多个类型参数
可以定义多个类型参数,适用于需要处理多种类型关系的场景。
示例:键值对存储
class KeyValuePair<K, V> {
constructor(public key: K, public value: V) {}
display(): void {
console.log(`Key: ${this.key}, Value: ${this.value}`);
}
}
// 使用
const pair = new KeyValuePair<number, string>(1, "TypeScript");
pair.display(); // 输出: "Key: 1, Value: TypeScript"
const anotherPair = new KeyValuePair<string, boolean>("isDone", true);
anotherPair.display(); // 输出: "Key: isDone, Value: true"
说明
- 使用多个类型参数
K
和V
,让KeyValuePair
同时支持键值对的灵活定义。 - 在实际应用中,类似的场景常见于实现 Map 或缓存结构。
3. 泛型约束
有时,我们需要对泛型参数进行约束,要求它必须满足某些条件(如实现某个接口)。
示例:约束为特定接口
interface Lengthwise {
length: number;
}
class LengthChecker<T extends Lengthwise> {
checkLength(item: T): void {
console.log(`Length: ${item.length}`);
}
}
// 使用
const checker = new LengthChecker<{ length: number; name: string }>();
checker.checkLength({ length: 10, name: "Test" }); // 输出: "Length: 10"
// 错误示例: 类型不满足约束
// checker.checkLength(123); // Error: 类型“number”不满足约束“Lengthwise”。
说明
T extends Lengthwise
限制了T
必须有length
属性。- 这种约束提高了类型安全性,确保只有符合条件的类型才能被使用。
4. 泛型和静态成员
泛型参数是作用于类的实例级别,而不是静态成员级别。
静态属性不能使用泛型类型
示例:静态成员与泛型的区别
class StaticExample<T> {
static staticProperty: string = "Static Property";
instanceProperty: T;
constructor(value: T) {
this.instanceProperty = value;
}
}
// 静态属性不能访问泛型类型
console.log(StaticExample.staticProperty); // 输出: "Static Property"
// 实例属性使用泛型
const example = new StaticExample<number>(42);
console.log(example.instanceProperty); // 输出: 42
说明
- 泛型无法直接约束或作用于静态属性
- 因为静态成员在类加载时初始化,而泛型类型是在实例化时确定。
5. 泛型类与接口的结合
可以用泛型类实现泛型接口,从而增强代码的灵活性。
示例:泛型类实现泛型接口
interface Repository<T> {
add(item: T): void;
getAll(): T[];
}
class InMemoryRepository<T> implements Repository<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getAll(): T[] {
return this.items;
}
}
// 使用
const repo = new InMemoryRepository<string>();
repo.add("TypeScript");
repo.add("Generics");
console.log(repo.getAll()); // 输出: ["TypeScript", "Generics"]
说明
Repository
接口通过泛型T
描述了方法的类型约束。InMemoryRepository
实现该接口时具体化了类型。
6. 泛型与继承
泛型可以与继承结合,形成更复杂的类型约束。
示例:带约束的继承
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
bark(): void {
console.log("Woof!");
}
}
class AnimalShelter<T extends Animal> {
private animals: T[] = [];
addAnimal(animal: T): void {
this.animals.push(animal);
}
getAllAnimals(): T[] {
return this.animals;
}
}
// 使用
const dogShelter = new AnimalShelter<Dog>();
dogShelter.addAnimal(new Dog("Rex"));
console.log(dogShelter.getAllAnimals()); // 输出: [Dog { name: 'Rex' }]
说明
T extends Animal
限制了T
必须是Animal
或其子类。- 确保了 shelter 只能管理动物或其派生类的实例。
7. 泛型工具:Partial 和 Readonly
TypeScript 提供了一些内置工具类型,可结合泛型类提升灵活性。
示例:Partial 和 Readonly
class User {
constructor(public id: number, public name: string) {}
}
// Partial
function updateUser(user: Partial<User>): User {
return { id: user.id ?? 1, name: user.name ?? "Default" };
}
console.log(updateUser({ name: "Updated Name" })); // 输出: { id: 1, name: "Updated Name" }
// Readonly
class ReadonlyRepository<T> {
private items: ReadonlyArray<T>;
constructor(items: T[]) {
this.items = items;
}
getAll(): ReadonlyArray<T> {
return this.items;
}
}
// 使用
const readonlyRepo = new ReadonlyRepository<User>([
new User(1, "Alice"),
new User(2, "Bob"),
]);
console.log(readonlyRepo.getAll()); // 输出: [User { id: 1, name: 'Alice' }, User { id: 2, name: 'Bob' }]
说明
Partial
创建可选类型的泛型工具,非常适合更新操作。Readonly
防止对象的属性被修改,提高数据安全性。
总结
- 泛型的核心作用 是让代码更加通用和灵活,特别适用于需要多样化类型适配的场景。
- 类型约束 提高了类型安全性,确保泛型的实际类型符合期望。
- 实践场景:如数据存储、管理结构(队列、堆栈、集合)、工具函数(Partial、Readonly)等。