泛型基础
泛型(Generics)是TypeScript中一种特殊的类型,它允许我们创建可重用的组件,这些组件可以适应任何数据类型。泛型的基本语法是在组件名称后使用尖括号 < >
包围类型参数。
例如,我们可以定义一个泛型函数,该函数接受两个参数,并返回它们的和:
function add<T>(x: T, y: T): T {
if (typeof x === 'number' && typeof y === 'number') {
return x + y;
}
return '无法相加';
}
console.log(add<string>('Hello', 'World')); // 输出: '无法相加'
console.log(add<number>(1, 2)); // 输出: 3
在上面的例子中,<T>
是泛型参数,表示这个函数可以适用于任何类型。
泛型约束
为了增强类型的安全性,TypeScript 提供了泛型约束。这意味着我们可以限制泛型参数的类型。例如,我们可以创建一个泛型函数,它接受一个对象和一个键,然后返回这个对象上该键的值。
interface Lengthwise {
length: number;
[prop: string]: any;
}
function getLength<T extends Lengthwise>(obj: T): T['length'] {
return obj.length;
}
console.log(getLength({ length: 5, a: 'hello' })); // 输出: 5
在上面的例子中,T extends Lengthwise
表明泛型参数 T
必须是 Lengthwise
类型或 Lengthwise
的子类型。
泛型与类型别名
泛型可以与类型别名一起使用,以创建可复用的类型。例如,我们可以定义一个泛型接口,表示一个映射:
type MapObj<T, U> = {
[key: string]: T;
} & {
(): U;
};
const myMap: MapObj<number, string> = () => 'Hello';
console.log(myMap['1']); // 输出: undefined
console.log(myMap()); // 输出: 'Hello'
在上面的例子中,MapObj
是一个泛型接口,它定义了一个对象,该对象有字符串键和对应的值,以及一个返回 U
类型值的函数。
泛型与类
泛型也可以用于类,以实现代码复用。例如,我们可以定义一个泛型类,用于创建不同类型的栈:
class Stack<T> {
private items: T[] = [];
push(item: T) {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const intStack = new Stack<number>();
intStack.push(1);
intStack.push(2);
console.log(intStack.pop()); // 输出: 2
const strStack = new Stack<string>();
strStack.push('Hello');
strStack.push('World');
console.log(strStack.pop()); // 输出: 'World'
在上面的例子中,Stack
是一个泛型类,它使用泛型参数 T
来定义栈中元素的类型。
泛型的高级用法
泛型在TypeScript中还有很多高级用法,例如递归泛型、条件类型、映射类型等。这些功能可以让我们更灵活地使用泛型,创建更复杂的组件和类型。
递归泛型
递归泛型允许泛型类型引用自己。例如,我们可以创建一个泛型接口,表示一个嵌套的数组:
type NestedArray<T> = T | (Array<NestedArray<T>>>;
const arr: NestedArray<number> = [1, [2, [3, 4]]];
条件类型
条件类型允许我们根据某些条件选择类型。例如,我们可以创建一个函数,它根据参数的类型返回不同的类型:
type IsString<T> = T extends string ? T : never;
type Result = IsString<number>; // Result 类型为 'never'
type Result2 = IsString<'hello'>; // Result2 类型为 'hello'
映射类型
映射类型允许我们对对象的每个属性应用一个函数。例如,我们可以创建一个函数,它接受一个对象,并返回一个新的对象,其中每个属性的值都被包装在一个数组中:
type Mapper<T> = {
[P in keyof T]: [T[P]];
};
type Result = Mapper<{ a: number, b: string }>;
// Result 类型为 { a: [number]; b: [string]; }
在上面的例子中,Mapper
是一个映射类型,它接受一个对象 T
,并返回一个新的对象,其中每个属性的值都被包装在一个数组中。
总结
泛型是TypeScript中一种强大的工具,它允许我们创建可重用的组件,这些组件可以适应任何数据类型。泛型可以与类型别名、接口、类一起使用,以实现代码复用。通过泛型约束、递归泛型、条件类型、映射类型等高级用法,我们可以更灵活地使用泛型,创建更复杂的组件和类型。
上一章:字面量类型与索引签名 下一章:类型守卫与类型断言详解