泛型(Generics) 是 TypeScript 中一个非常强大的特性,它允许我们在编写代码时提供类型参数,使得代码更加灵活和可重用。今天,我们将详细探讨 TypeScript 中的泛型,帮助大家更好地理解并应用这一特性。
什么是泛型?
在 TypeScript 中,泛型允许我们在定义函数、接口、类等时,不预先指定具体的类型,而是使用一个或多个类型参数,直到实际使用时才确定这些类型。这种类型参数化的方式,使得我们的代码在保持类型安全的同时,更加通用和灵活。
泛型的基本语法
泛型的语法非常简单,通常是通过尖括号(<>)来指定类型参数。以下是一个函数的基本泛型示例:
function identity<T>(value: T): T {
return value;
}
在这个示例中,T 就是一个类型参数。我们在函数调用时不需要指定具体的类型(当然指定类型也是可以的),TypeScript 会根据传入的值自动推断类型:
let result1 = identity(42); // result1的类型为 number
let result2 = identity("Hello"); // result2的类型为 string
let result3 = identity<number>(42); // result1的类型为 number
let result4 = identity<string>("Hello"); // result2的类型为 string
通过泛型,我们使得 identity 函数能够处理任意类型的值,并保证返回值的类型与输入值一致。
泛型的应用
1. 泛型函数
泛型函数最常见的应用场景之一是,当我们需要处理不确定类型的数据时,可以使用泛型函数来确保类型安全。例如:
function logLength<T>(value: T[]): number {
return value.length;
}
console.log(logLength([1, 2, 3])); // 输出: 3
console.log(logLength(["a", "b", "c"])); // 输出: 3
这里,logLength 函数接受一个数组 T[],并返回数组的长度。在调用时,T 会被推导为传入数组的元素类型。
2. 泛型接口
泛型不仅可以用在函数中,也可以用在接口中。通过泛型接口,我们可以定义更加灵活且通用的结构。例如:
interface Pair<T, U> {
first: T;
second: U;
}
let pair: Pair<string, number> = { first: "Hello", second: 42 };
在这个例子中,Pair 接口有两个类型参数 T 和 U,表示一个包含两个元素的对,分别可以是不同的类型。我们在创建 pair 对象时指定了具体的类型:string 和 number。
3. 泛型类
泛型也可以应用到类中,让类的实例类型更加灵活。例如:
class Box<T> {
value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const numberBox = new Box(42); // numberBox的类型为 Box<number>
const stringBox = new Box("Hello"); // stringBox的类型为 Box<string>
在这个例子中,Box 是一个泛型类,它接收一个类型参数 T,用于指定实例属性 value 的类型。我们可以在创建 Box 类的实例时指定不同的类型。
4. 泛型约束
有时候,我们希望泛型的类型参数满足某些条件,可以使用泛型约束来限制类型。例如,我们可以要求泛型类型参数必须是某个接口的实现类型:
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(value: T): number {
return value.length;
}
console.log(logLength([1, 2, 3])); // 输出: 3
console.log(logLength("Hello")); // 输出: 5
在这个例子中,T extends Lengthwise 表示类型 T 必须具有 length 属性。这样就确保了我们只传入那些具有 length 属性的类型(如数组、字符串等)。
泛型的高级用法
除了基本的应用外,泛型在实际开发中还具有一些更为复杂的用法,以下是两个常见的例子:
1. 多个类型参数
泛型不仅可以有一个类型参数,还可以有多个类型参数。例如,以下是一个接受两个类型参数的泛型函数:
function createPair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
let pair = createPair("Hello", 42); // pair的类型为 [string, number]
在这个例子中,createPair 函数接受两个参数,返回一个包含两个元素的元组,分别是 T 类型和 U 类型。
2. 使用 keyof 和 infer 提取类型
TypeScript 还提供了 keyof 和 infer 操作符,它们与泛型结合可以实现更复杂的类型推断。
例如,使用 keyof 可以获取一个对象的键名类型:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "Alice", age: 30 };
let name = getProperty(person, "name"); // name的类型为 string
在这个例子中,K extends keyof T 表示 K 必须是 T 类型的一个属性名。这样我们确保了在调用 getProperty 时,key 参数一定是对象 obj 中的一个有效属性名。
总结
TypeScript 的泛型是一个非常强大和灵活的工具,它使得我们能够编写更加通用、可重用的代码,同时保持类型安全。通过泛型,我们可以创建更为通用的函数、接口和类,处理不同类型的数据,而不需要牺牲类型检查的强大功能。