likes
comments
collection
share

简析TypeScript 泛型及其应用

作者站长头像
站长
· 阅读数 13

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" 推断出类型参数 Tstring 类型。

泛型约束

有时我们希望泛型参数具有某些特定的属性或方法。为了实现这个目标,我们可以使用泛型约束。

泛型约束可以通过 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 的泛型函数。这个函数接受两个类型参数 TU,然后将它们组合成一个元组。在调用这个函数时,我们需要显式地传入类型参数,如 <number, string>

我们也可以让 TypeScript 自动推断类型参数:

let result = combine(1, "hello");

在这种情况下,TypeScript 会根据传入的参数 1"hello" 推断出类型参数 Tnumber 类型,Ustring 类型。

泛型在类和接口中的应用

泛型不仅可以应用于函数,还可以应用于类和接口。下面我们来看一个泛型在类和接口中的应用示例:

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 中的泛型是一种非常有用的特性,它可以让我们编写更通用、更灵活的函数、类和接口。通过泛型,我们可以在不损失类型安全的前提下,提高代码的复用性和可维护性。

TS常用工具类型整理