TypeScript - 泛型类
你好,未来的编程超级巨星们!今天,我们将深入TypeScript泛型类的精彩世界。如果你是编程新手,不用担心;我会像过去几年里指导无数学生一样,一步步引导你。所以,拿起你最喜欢的饮料,舒服地坐好,让我们一起开始这段冒险!
泛型类
什么是泛型类?
想象你在一个冰淇淋店,但不是在选择口味,而是在挑选数据类型。这就是泛型类的本质!它们允许我们创建灵活、可重用的组件,可以处理不同的数据类型,同时不牺牲类型安全。
让我们从一个简单的例子开始:
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getValue(): T {
return this.content;
}
}
在这个例子中,Box
是一个泛型类。<T>
就像是一个占位符,我们稍后会指定类型。这就像告诉冰淇淋店:“我下单时会决定口味!”
让我们分解一下:
-
class Box<T>
: 这声明了一个名为Box
的泛型类,有一个类型参数T
。 -
private content: T
: 我们说content
将是类型T
,无论T
是什么。 -
constructor(value: T)
: 构造函数接受一个类型为T
的值。 -
getValue(): T
: 这个方法返回一个类型为T
的值。
现在,让我们看看如何使用这个类:
let numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // 输出: 42
let stringBox = new Box<string>("你好,TypeScript!");
console.log(stringBox.getValue()); // 输出: 你好,TypeScript!
这很酷吧?我们使用了同一个 Box
类来存储数字和字符串。这就像有一个魔法的盒子,可以装任何你放进去的东西,但仍然记得它具体装的是什么类型的物品!
多个类型参数
有时,一个类型参数不够。让我们创建一个更复杂的例子,使用多个类型参数:
class Pair<T, U> {
private first: T;
private second: U;
constructor(first: T, second: U) {
this.first = first;
this.second = second;
}
getFirst(): T {
return this.first;
}
getSecond(): U {
return this.second;
}
}
这个 Pair
类可以持有两个可能是不同类型的数据。这就像有一个双人冰淇淋蛋筒,每个球可以是不同的口味!
让我们使用我们的 Pair
类:
let pair = new Pair<string, number>("年龄", 30);
console.log(pair.getFirst()); // 输出: 年龄
console.log(pair.getSecond()); // 输出: 30
泛型约束
有时,我们想要限制可以在我们的泛型类中使用哪些类型。我们可以通过使用约束来实现这一点。这就像说:“你可以选择任何冰淇淋口味,只要它不是太辣的!”
interface Lengthwise {
length: number;
}
class LengthChecker<T extends Lengthwise> {
checkLength(obj: T): string {
return `长度是: ${obj.length}`;
}
}
在这个例子中,T extends Lengthwise
意味着 T
必须是一个有 length
属性的类型。让我们使用它:
let stringChecker = new LengthChecker<string>();
console.log(stringChecker.checkLength("你好")); // 输出: 长度是: 2
let arrayChecker = new LengthChecker<number[]>();
console.log(arrayChecker.checkLength([1, 2, 3])); // 输出: 长度是: 3
// 这会导致错误:
// let numberChecker = new LengthChecker<number>();
// 类型 'number' 不满足约束 'Lengthwise'。
在泛型类中实现泛型接口
现在,让我们通过在泛型类中实现泛型接口来提升我们的技能。这就像为不同类型的冰淇淋创建一个食谱(接口)!
首先,让我们定义一个泛型接口:
interface Repository<T> {
getById(id: number): T;
save(item: T): void;
}
这个 Repository
接口定义了处理数据存储和检索的类的契约。现在,让我们用泛型类实现这个接口:
class GenericRepository<T> implements Repository<T> {
private items: T[] = [];
getById(id: number): T {
return this.items[id];
}
save(item: T): void {
this.items.push(item);
}
}
我们的 GenericRepository
类实现了 Repository
接口。它可以处理任何类型 T
。让我们使用它:
interface User {
name: string;
age: number;
}
let userRepo = new GenericRepository<User>();
userRepo.save({ name: "Alice", age: 30 });
userRepo.save({ name: "Bob", age: 25 });
console.log(userRepo.getById(0)); // 输出: { name: "Alice", age: 30 }
console.log(userRepo.getById(1)); // 输出: { name: "Bob", age: 25 }
在这个例子中,我们创建了一个用于 User
对象的仓库。但我们泛型实现的美丽之处在于,我们同样可以轻松地为任何其他类型创建仓库!
方法表
以下是我们所涵盖方法的便捷表格总结:
方法 | 描述 | 示例 |
---|---|---|
constructor(value: T) |
创建泛型类的新实例 | new Box<number>(42) |
getValue(): T |
返回泛型类中存储的值 | numberBox.getValue() |
getFirst(): T |
返回对中的第一个值 | pair.getFirst() |
getSecond(): U |
返回对中的第二个值 | pair.getSecond() |
checkLength(obj: T): string |
检查对象(有约束)的长度 | stringChecker.checkLength("你好") |
getById(id: number): T |
通过 ID 从仓库中检索项目 | userRepo.getById(0) |
save(item: T): void |
将项目保存到仓库 | userRepo.save({ name: "Alice", age: 30 }) |
就这样,大家!我们一起穿越了TypeScript泛型类的领域,从基本的盒子到复杂的仓库。记住,熟能生巧,所以不要害怕尝试这些概念。谁知道呢?你可能会创造出编程界的下一个大事件!下次见,快乐编程!
Credits: Image by storyset