泛型(Generics)是 TypeScript 中的一种强大工具,它允许你在定义函数、类或接口时,不预先指定具体的类型,而是在使用时再指定。这样可以提高代码的灵活性和复用性。
泛型函数
- 泛型的语法是 <> 里写类型参数,一般可以用 T 来表示
- 在函数中使用泛型,可以让函数适用于多种类型。
function identity<T>(arg: T): T {
return arg;
}
- 函数 identity 使用了泛型类型参数 T, 参数 arg 的类型为 T,返回值的类型也是 T,表示参数和返回值的类型可以是任何类型。
- 泛型的语法是 <> 里写类型参数,一般可以用 T 来表示。T就是个占位符,在使用时可以把类型像参数一样传入
- 简单使用
// 创建泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数。<string>表示传递类型是string
let output = identity<string>("myString");
console.log(output); // myString
// 使用类型推断,自动推断泛型类型
let output2 = identity("myString");
console.log(output2); // myString
一个函数定义多个泛型参数
- 使用尖括号 <> 来定义多个泛型参数,多个参数之间用逗号 , 分隔
function test<T, K>(a: T, b: K): K{
return b;
}
test<number, string>(10, "hello");// hello
泛型接口
- 在定义接口时, 为接口中的属性或方法定义泛型类型
- 在使用接口时, 再指定具体的泛型类型
// 定义一个泛型接口
interface DataStorage<T> {
data: T[]; // 存储数据的数组,类型为 T
addItem: (item: T) => void; // 添加数据的方法
getItems: () => T[]; // 获取所有数据的方法
}
// 实现这个泛型接口
class SimpleStorage<T> implements DataStorage<T> {
data: T[] = []; // 初始化一个空数组来存储数据
// 添加数据的方法
addItem(item: T): void {
this.data.push(item);
}
// 获取所有数据的方法
getItems(): T[] {
return this.data;
}
}
// 使用这个泛型类存储不同类型的数据
const numberStorage = new SimpleStorage<number>(); // 存储数字
numberStorage.addItem(10);
numberStorage.addItem(20);
console.log(numberStorage.getItems()); // 输出: [10, 20]
const stringStorage = new SimpleStorage<string>(); // 存储字符串
stringStorage.addItem("Hello");
stringStorage.addItem("World");
console.log(stringStorage.getItems()); // 输出: ['Hello', 'World']
泛型类
- 在定义类时, 为类中的属性或方法定义泛型类型
- 在创建类的实例时, 再指定特定的泛型类型
// 定义一个泛型类 Box
class Box<T> {
content: T; // T 表示盒子里装的物品的类型
// 构造函数接受一个参数,类型为 T
constructor(value: T) {
this.content = value;
}
// 获取盒子里内容的方法
getContent(): T {
return this.content;
}
}
// 使用这个泛型类
// 创建一个装有数字的盒子
const numberBox = new Box<number>(42);
console.log(numberBox.getContent()); // 输出: 42
// 创建一个装有字符串的盒子
const stringBox = new Box<string>("Hello, TypeScript!");
console.log(stringBox.getContent()); // 输出: Hello, TypeScript!
// 创建一个装有数组的盒子
const arrayBox = new Box<number[]>([1, 2, 3]);
console.log(arrayBox.getContent()); // 输出: [1, 2, 3]
泛型约束
- 通过泛型约束,我们可以对泛型的类型进行限制,而不是允许它接受任意类型。
- extends 关键字: 用于对类型参数进行约束,使其必须是某个类型的子类型,或者必须实现某个接口。 这不仅限制了类型参数的种类,也确保了类型安全。
- 接口约束: 可以使用接口来定义类型参数的约束。
// 定义一个接口 Printable,指定实现该接口的类/对象必须包含一个名为 print 的方法
// 该方法不接受任何参数(即无参数),且不返回任何值(返回类型为 void)
interface Printable {
// print 方法签名:无参数,返回 void
print(): void;
}
// 定义一个泛型函数 printItem,带有类型参数 T
// T 被限制为必须扩展(实现)Printable 接口(通过 extends Printable 实现)
// 这意味着,只有当 T 是 Printable 的子类型(即实现了 Printable 接口)时,才能作为 printItem 的类型参数
function printItem<T extends Printable>(
// 函数参数 item 的类型被指定为 T,即 Printable 的子类型
item: T
): void {
// 在函数体内,安全地调用 item 的 print 方法,因为 TypeScript 确信 T 实现了 Printable
item.print();
}
// 定义一个接口,包含我们希望对象必须有的属性
interface HasLength {
length: number;
}
// 使用泛型约束,限制 T 必须是 extends HasLength 的类型
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length); // 现在可以安全访问 length 属性
}
// 使用示例
// 字符串有 length 属性
logLength("Hello, TypeScript!"); // 输出: 19
// 数组也有 length 属性
logLength([1, 2, 3]); // 输出: 3
// 自定义对象,只要包含 length 属性也可以
logLength({ length: 7 }); // 输出: 7
// 下面这行代码会报错,因为数字没有 length 属性
// logLength(42); // 错误: 数字类型不满足 HasLength 约束
- 类约束: 可以约束类型参数必须是某个类的子类型。
// 定义一个 Animal 类
class Animal {
// 属性:name,类型为 string
name: string;
// 构造函数,初始化 name 属性
constructor(name: string) {
this.name = name;
}
// 方法:move,接受一个可选参数 distanceInMeters(默认值为 0),打印移动信息
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
// 定义一个泛型函数 createAnimalSound
// 类型参数 T 被约束为必须是 Animal 的子类型(通过 extends Animal 实现)
// 这意味着 T 可以是 Animal 本身,也可以是 Animal 的任何子类
function createAnimalSound<T extends Animal>(
// 函数参数 animal 的类型被指定为 T,即 Animal 的子类型
animal: T
): string {
// 函数体:返回一个字符串,包含 animal 的 name 和一句固定描述
// 由于 T extends Animal,TypeScript 确信 animal 有一个名为 name 的属性
return animal.name + " makes a sound";
}