简析TypeScript 泛型及其应用
TypeScript 是 JavaScript 的一个超集,它在 JavaScript 的基础上增加了类型系统。类型系统可以帮助我们在开发过程中发现潜在的错误,提高代码质量。在本篇博客中,我们将详细讲解 TypeScript 中的泛型及其应用。
什么是泛型?
泛型(Generic)是 TypeScript 提供的一个强大的工具,它可以让你在定义函数、接口或类时,不预先指定具体的类型,而是在使用时再确定类型。这样可以增加代码的复用性,同时保持类型安全。
泛型的一个典型应用场景是编写一个通用的函数,这个函数可以处理不同类型的数据,但是又不会丢失类型信息。例如,我们可以编写一个泛型函数,用于返回数组中的最小值。
泛型的基本用法
下面我们来看一个简单的泛型示例:
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString");
在这个示例中,我们定义了一个名为 identity
的泛型函数。这个函数接受一个类型参数 T
,然后使用这个类型参数来定义输入参数 arg
和返回值。在调用这个函数时,我们需要显式地传入类型参数,如 <string>
。
我们也可以让 TypeScript 自动推断类型参数:
let output = identity("myString");
在这种情况下,TypeScript 会根据传入的参数 "myString"
推断出类型参数 T
为 string
类型。
泛型约束
有时我们希望泛型参数具有某些特定的属性或方法。为了实现这个目标,我们可以使用泛型约束。
泛型约束可以通过 extends
关键字来定义。例如,我们可以定义一个泛型约束,要求泛型参数具有 length
属性:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
在这个示例中,我们定义了一个名为 Lengthwise
的接口,它包含一个 length
属性。然后我们定义了一个泛型约束 T extends Lengthwise
,这表示泛型参数 T
必须具有 Lengthwise
接口所定义的属性。现在,当我们调用 loggingIdentity
函数时,传入的参数必须满足泛型约束。
loggingIdentity({ length: 10, value: 3 }); // 正确
loggingIdentity(3); // 错误:number 类型没有 length 属性
泛型在函数中的应用
泛型在函数中的应用非常广泛,它们可以让我们编写更通用、更灵活的函数。下面我们来看一个泛型在函数中的应用示例:
function combine<T, U>(arg1: T, arg2: U): [T, U] {
return [arg1, arg2];
}
let result = combine<number, string>(1, "hello");
在这个示例中,我们定义了一个名为 combine
的泛型函数。这个函数接受两个类型参数 T
和 U
,然后将它们组合成一个元组。在调用这个函数时,我们需要显式地传入类型参数,如 <number, string>
。
我们也可以让 TypeScript 自动推断类型参数:
let result = combine(1, "hello");
在这种情况下,TypeScript 会根据传入的参数 1
和 "hello"
推断出类型参数 T
为 number
类型,U
为 string
类型。
泛型在类和接口中的应用
泛型不仅可以应用于函数,还可以应用于类和接口。下面我们来看一个泛型在类和接口中的应用示例:
interface GenericIdentityFn {
<T>(arg: T): T;
}
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
return x + y;
};
在这个示例中,我们首先定义了一个名为 GenericIdentityFn
的泛型接口。这个接口包含一个泛型方法,接受一个类型参数 T
。接着,我们定义了一个名为 GenericNumber
的泛型类。这个类包含一个泛型属性 zeroValue
和一个泛型方法 add
。在创建 GenericNumber
的实例时,我们需要指定泛型参数,如 <number>
。
泛型的高级用法
泛型还有一些高级用法,如条件类型、映射类型和类型推断等。下面我们来看一个条件类型的示例:
type ElementType<T> = T extends Array<infer U> ? U : never;
type A = ElementType<number[]>; // A 类型为 number
type B = ElementType<string[]>; // B 类型为 string
在这个示例中,我们定义了一个名为 ElementType
的条件类型。这个类型接受一个泛型参数 T
,然后通过 T extends Array<infer U> ? U : never
来判断T
是否为数组。如果是数组,它会提取数组元素的类型;如果不是,它会返回never
类型。这个条件类型可以帮助我们轻松地从数组类型中提取元素类型。
接下来,我们来看一个映射类型的示例:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type ReadonlyPerson = Readonly<{
name: string;
age: number;
}>;
在这个示例中,我们定义了一个名为 Readonly
的映射类型。这个类型接受一个泛型参数 T
,然后将 T
的所有属性变为只读属性。这个映射类型可以帮助我们轻松地创建一个只读版本的类型。
总结
TypeScript 中的泛型是一种非常有用的特性,它可以让我们编写更通用、更灵活的函数、类和接口。通过泛型,我们可以在不损失类型安全的前提下,提高代码的复用性和可维护性。
转载自:https://juejin.cn/post/7216258954912661561