TypeScript -泛型类

你好,未来的编程巨星们!今天,我们将深入TypeScript泛型类的精彩世界。如果你是编程新手,不用担心;我会像过去几年指导无数学生一样,一步步引导你。所以,拿起你最喜欢的饮料,舒服地坐下来,让我们一起踏上这段冒险之旅吧!

TypeScript - Generic Classes

泛型类

什么是泛型类?

想象你在一个冰淇淋店,但不是选择口味,而是挑选数据类型。这就是泛型类的本质!它们允许我们创建灵活、可重用的组件,可以处理不同的数据类型,同时不牺牲类型安全。

让我们从一个简单的例子开始:

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("你好")); // 输出: 长度是: 3

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