ZetCode

TypeScript 泛型

最后修改于 2025 年 3 月 5 日

TypeScript 中的泛型能够创建可重用、类型安全组件。它们允许您定义适用于多种类型的函数、类和接口。本教程将通过实际示例探讨泛型。

泛型是类型的占位符。它们允许您编写灵活且可重用的代码,而不会牺牲类型安全。泛型使用尖括号(<T>)定义。

基本泛型函数

此示例展示了一个返回输入值的简单泛型函数。

basic_generic.ts
function identity<T>(arg: T): T {
    return arg;
}

console.log(identity<number>(42));  // Output: 42
console.log(identity<string>("Hello"));  // Output: Hello

在此示例中,identity 函数使用泛型类型 T,使其能够接受并返回在调用站点指定的任何类型。通过在尖括号中显式提供 numberstring,我们为每次调用定义 T 所代表的含义。TypeScript 通过匹配参数和返回类型来确保类型安全,输出显示的值与输入值相同。这说明了泛型如何在不丢失编译时检查的情况下实现类型灵活性。

泛型数组

泛型可与数组一起使用以确保类型安全。此示例演示了一个处理数组的泛型函数。

generic_array.ts
function reverseArray<T>(arr: T[]): T[] {
    return arr.reverse();
}

console.log(reverseArray<number>([1, 2, 3]));  // Output: [3, 2, 1]
console.log(reverseArray<string>(["a", "b", "c"]));  // Output: ["c", "b", "a"]

reverseArray 函数使用泛型类型 T 来指定输入数组 T[] 中元素的类型,并返回相同类型的数组。当使用数字数组或字符串数组调用时,TypeScript 会强制所有元素都匹配指定的类型 T。应用 reverse() 方法后,输出显示了反转后的数组。这说明了泛型如何跨数组操作保持类型一致性。

泛型接口

接口也可以使用泛型。此示例为键值对定义了一个泛型接口。

generic_interface.ts
interface KeyValuePair<K, V> {
    key: K;
    value: V;
}

const pair: KeyValuePair<string, number> = { key: "age", value: 30 };
console.log(pair);  // Output: { key: "age", value: 30 }

KeyValuePair 接口使用两种泛型类型,K 代表键,V 代表值,使其可用于任何键值组合。在此实例中,将 string 分配给 K,将 number 分配给 V,从而为 pair 对象创建特定类型。TypeScript 确保对象符合此结构,输出显示了定义的键值对。这表明泛型接口如何提供可重用的类型定义。

泛型类

类可以使用泛型来创建可重用组件。此示例展示了一个泛型堆栈类。

generic_class.ts
class Stack<T> {
    private items: T[] = [];

    push(item: T): void {
        this.items.push(item);
    }

    pop(): T | undefined {
        return this.items.pop();
    }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop());  // Output: 2

Stack 类使用泛型类型 T 来定义它可以容纳的项的类型。items 数组的类型为 T[],并且 pushpop 等方法在此类型上运行。当实例化为 Stack<number> 时,它只接受数字,从而确保类型安全。该示例推入两个数字并弹出最后一个,输出显示 2。这突出显示了泛型类如何实现类型特定的可重用数据结构。

泛型约束

约束限制了可与泛型一起使用的类型。此示例确保泛型类型具有 length 属性。

generic_constraints.ts
interface Lengthwise {
    length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
    console.log(arg.length);
}

logLength("Hello");  // Output: 5
logLength([1, 2, 3]);  // Output: 3

logLength 函数使用泛型类型 T,并受 extends Lengthwise 约束,这意味着 T 必须具有类型为 numberlength 属性。这使得该函数可以处理字符串和数组(两者都有 length),但不能处理不兼容的类型(如数字)。输出显示了字符串(5)和数组(3)的长度,展示了约束如何确保类型兼容性同时保持灵活性。

泛型实用工具类型

TypeScript 提供了内置的实用工具类型,如 PartialReadonly。此示例演示了它们的用法。

utility_types.ts
interface User {
    name: string;
    age: number;
}

const partialUser: Partial<User> = { name: "John" };
const readonlyUser: Readonly<User> = { name: "Jane", age: 25 };

console.log(partialUser);  // Output: { name: "John" }
console.log(readonlyUser);  // Output: { name: "Jane", age: 25 }

此示例使用 TypeScript 的内置泛型实用工具类型。Partial<User> 使 User 的所有属性都成为可选的,因此 partialUser 可以省略 ageReadonly<User> 使所有属性成为只读的,防止在初始化 readonlyUser 后进行修改。TypeScript 在编译时强制执行这些约束,输出显示了结果对象。这展示了实用工具类型如何简化常见的类型转换。

具有多种类型的泛型函数

泛型可以处理多种类型。此示例展示了一个组合两种不同类型值的函数。

multiple_types.ts
function merge<T, U>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

const result = merge({ name: "Alice" }, { age: 30 });
console.log(result);  // Output: { name: "Alice", age: 30 }

merge 函数使用两种泛型类型 TU 来表示两个输入对象的类型。它使用展开运算符返回一个交集类型 T & U,合并它们的属性。在这种情况下,T 被推断为 { name: string }U 被推断为 { age: number },从而生成一个合并的对象。输出显示了合并的属性,说明了多种泛型类型如何实现灵活的对象组合。

泛型默认类型

泛型可以具有默认类型。此示例定义了一个带有默认类型的泛型函数。

default_types.ts
function createArray<T = string>(length: number, value: T): T[] {
    return Array(length).fill(value);
}

console.log(createArray(3, "a"));  // Output: ["a", "a", "a"]
console.log(createArray<number>(3, 1));  // Output: [1, 1, 1]

createArray 函数使用泛型类型 T,其默认值为 string。如果未指定类型,T 将默认为 string,如第一次调用所示。当在第二次调用中显式设置为 number 时,它会覆盖默认值。该函数创建指定长度并填充给定值的数组,输出显示了这两种情况。这说明了当期望常见类型时,默认类型如何提高可用性。

泛型类型别名

类型别名可以是泛型的,从而提供了一种定义可重用类型模式的方法。此示例展示了一个结果类型的泛型类型别名。

generic_type_alias.ts
type Result<T> = { success: true; value: T } | { success: false; error: string };

const successResult: Result<number> = { success: true, value: 42 };
const errorResult: Result<string> = { success: false, error: "Not found" };

console.log(successResult);  // Output: { success: true, value: 42 }
console.log(errorResult);   // Output: { success: false, error: "Not found" }

Result 类型别名使用泛型类型 T 来定义成功和错误情况的联合。对于 successResultTnumber,表示具有值的成功结果。对于 errorResultTstring,但错误情况使用固定的 error 属性。TypeScript 确保每个对象都符合联合的一种形状,输出显示了两种可能的状态。这说明了泛型类型别名如何创建灵活、可重用的类型定义。

泛型条件类型

条件类型允许泛型根据条件进行适应。此示例提取函数的返回类型。

conditional_types.ts
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function greet(): string {
    return "Hello";
}

type GreetReturn = ReturnType<typeof greet>;
const message: GreetReturn = "Hello";
console.log(message);  // Output: Hello

ReturnType 泛型类型使用条件类型和 infer R 来提取函数的返回类型。如果 T 是一个函数,它会推断 R 为返回类型;否则为 never。将其应用于 greet 函数(通过 typeof greet),它解析为 string。因此,GreetReturn 类型为 string,允许将 message 分配给 "Hello"。输出确认了这一点,显示了条件类型如何实现动态类型推断。

泛型工厂函数

工厂函数可以使用泛型来创建不同类型的实例。此示例根据构造函数创建对象。

factory_functions.ts
class Animal {
    constructor(public name: string) {}
}

class Car {
    constructor(public model: string) {}
}

function createInstance<T>(ctor: new (arg: string) => T, arg: string): T {
    return new ctor(arg);
}

const dog = createInstance(Animal, "Dog");
const sedan = createInstance(Car, "Sedan");

console.log(dog.name);    // Output: Dog
console.log(sedan.model); // Output: Sedan

createInstance 函数使用泛型类型 T,并接受一个构造函数(new (arg: string) => T)和一个参数。它使用提供的构造函数创建 T 的实例。这里,AnimalCar 类使用特定参数进行了实例化,TypeScript 确保返回的实例与预期的类型匹配。输出显示了创建对象的属性,说明了泛型工厂函数如何支持类型安全的.*

最佳实践

来源

TypeScript 泛型文档

本教程通过实际示例介绍了 TypeScript 泛型。使用泛型来编写灵活、可重用且类型安全的.*

作者

我的名字是 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子.*

列出所有 TypeScript 教程