TypeScript 类与泛型的使用实践记录 | 豆包MarsCode AI刷题

90 阅读5分钟

TypeScript 类与泛型的使用实践记录

TypeScript 是 JavaScript 的超集,提供了静态类型、接口、类和泛型等强大功能,极大地增强了代码的可维护性和可读性。在这篇博客中,我们将深入探讨 TypeScript 中的泛型及其使用方法、场景,以及如何使用类型约束来增加代码的灵活性和安全性。

一、什么是泛型

泛型是指在定义函数、类或接口时,不预先指定具体的类型,而是使用类型参数(Type Parameter)来代替。这样可以使代码在处理不同类型时更加灵活,且不失去类型安全。泛型的引入使得代码的复用性大大增强。

1. 泛型的基本语法

泛型的基本语法如下:

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

在这个例子中,T 是一个类型参数,代表传入的参数类型。在调用这个函数时,可以根据实际传入的参数类型推导出 T 的具体类型。

2. 泛型的使用场景

泛型的应用场景非常广泛,以下是一些常见的使用场景:

  • 函数:当函数处理多种数据类型时,可以使用泛型。
  • :当类需要处理不同类型的属性或方法时,可以使用泛型。
  • 接口:当接口的属性值类型不确定时,可以使用泛型。

二、泛型函数的实践

1. 简单泛型函数

下面是一个简单的泛型函数示例,它可以返回传入参数的类型:

function log<T>(value: T): void {
    console.log(value);
}

log<string>("Hello, TypeScript!"); // 输出: Hello, TypeScript!
log<number>(42); // 输出: 42

在这个示例中,log 函数接收一个参数 value,并将其打印到控制台。我们可以在调用时指定参数的类型,也可以让 TypeScript 自动推导类型:

log("Hello, TypeScript!"); // 自动推导为 string

2. 使用泛型约束

有时我们希望限制泛型可以接受的类型,这时可以使用类型约束。比如,假设我们只希望传入的类型是一个具有 length 属性的对象:

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

logLength("Hello, TypeScript!"); // 输出: 17
logLength([1, 2, 3]); // 输出: 3
// logLength(42); // 错误: Type 'number' does not satisfy the constraint '{ length: number; }'.

在这个示例中,T 被约束为具有 length 属性的类型,这样我们就可以安全地使用 value.length

三、泛型类的实践

1. 简单泛型类

泛型类允许我们在类中使用类型参数。以下是一个简单的泛型类示例:

class Box<T> {
    private content: T;

    constructor(value: T) {
        this.content = value;
    }

    getContent(): T {
        return this.content;
    }
}

const stringBox = new Box<string>("Hello, TypeScript!");
console.log(stringBox.getContent()); // 输出: Hello, TypeScript!

const numberBox = new Box<number>(42);
console.log(numberBox.getContent()); // 输出: 42

在这个示例中,Box 类的构造函数接收一个参数 value,它的类型由类型参数 T 指定。这样,我们可以创建不同类型的 Box 实例,而不需要重复代码。

2. 泛型约束在类中的应用

我们还可以在类中使用泛型约束,以限制类型参数的类型。例如,假设我们希望 Box 的内容只能是具有 toString 方法的对象:

class StringBox<T extends { toString(): string }> {
    private content: T;

    constructor(value: T) {
        this.content = value;
    }

    getString(): string {
        return this.content.toString();
    }
}

const box = new StringBox<Date>(new Date());
console.log(box.getString()); // 输出: 当前日期和时间
// const invalidBox = new StringBox<number>(42); // 错误: Type 'number' does not satisfy the constraint '{ toString(): string; }'.

在这个示例中,T 被约束为具有 toString 方法的类型,因此我们可以安全地调用 toString 方法。

四、泛型接口的实践

1. 定义泛型接口

泛型接口允许我们定义一个可以接受不同类型的接口。以下是一个简单的泛型接口示例:

interface Pair<K, V> {
    key: K;
    value: V;
}

const pair: Pair<string, number> = {
    key: "age",
    value: 30,
};

console.log(pair); // 输出: { key: 'age', value: 30 }

2. 泛型接口的继承

泛型接口也可以被其他接口继承,以下是一个示例:

interface KeyValuePair<K, V> extends Pair<K, V> {
    description: string;
}

const kv: KeyValuePair<string, number> = {
    key: "age",
    value: 30,
    description: "User's age",
};

console.log(kv); // 输出: { key: 'age', value: 30, description: "User's age" }

在这个示例中,KeyValuePair 接口继承自 Pair 接口,并添加了一个 description 属性。

五、泛型的实际应用场景

1. 数据结构的实现

泛型在数据结构的实现中非常有用。例如,我们可以使用泛型实现一个栈(Stack)数据结构:

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

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

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

    peek(): T | undefined {
        return this.items[this.items.length - 1];
    }

    isEmpty(): boolean {
        return this.items.length === 0;
    }
}

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

2. API 请求的响应处理

在处理 API 请求时,泛型也可以帮助我们定义响应数据类型。例如:

interface ApiResponse<T> {
    data: T;
    error?: string;
}

function fetchData<T>(url: string): Promise<ApiResponse<T>> {
    return fetch(url)
        .then(response => response.json())
        .then(data => ({ data }))
        .catch(error => ({ data: null, error: error.message }));
}

fetchData<{ name: string; age: number }>("https://api.example.com/user")
    .then(response => {
        if (response.error) {
            console.error(response.error);
        } else {
            console.log(response.data.name); // 输出用户姓名
        }
    });

在这个示例中,fetchData 函数使用泛型来处理不同类型的 API 响应数据。

六、总结

在 TypeScript 中,泛型为我们提供了一种灵活、安全的方式来编写可重用的代码。通过使用泛型,我们可以创建函数、类和接口,能够处理多种数据类型,而不失去类型安全。

在实际开发中,利用泛型的类型约束功能,可以让我们的代码更加健壮,减少潜在的错误。无论是在实现数据结构、处理 API 响应,还是在构建复杂的组件时,泛型都能发挥重要作用。

希望通过本文的分享,能够帮助你更好地理解 TypeScript 中的泛型及其使用场景,从而在实际开发中灵活运用,提升代码的质量与可维护性。