TypeScript 泛型的使用实践记录 | 青训营

46 阅读2分钟

TypeScript 中的泛型是一种强大的工具,它可以在编写代码时提供更好的类型安全性和代码重用性。下面我将探讨一些 TypeScript 中泛型的使用方法和场景,以及如何使用类型约束来增加代码的灵活性和安全性。

1. 简单的泛型函数

假设我们有一个简单的需求,编写一个函数,接受一个数组,并返回数组中的第一个元素。我们可以使用泛型来实现这个函数:

function getFirstElement<T>(arr: T[]): T | undefined {
    return arr.length > 0 ? arr[0] : undefined;
}

const numbers = [1, 2, 3, 4, 5];
const firstNumber = getFirstElement(numbers); // 类型推断为 number | undefined

const strings = ['a', 'b', 'c'];
const firstString = getFirstElement(strings); // 类型推断为 string | undefined

2. 泛型约束

有时候我们想要对泛型进行一些约束,以确保泛型类型具有某些属性或方法。这可以通过使用泛型约束来实现。例如,我们想编写一个函数来查找数组中的最大值:

function findMaxValue<T extends number>(arr: T[]): T | undefined {
    if (arr.length === 0) {
        return undefined;
    }
    let max = arr[0];
    for (const val of arr) {
        if (val > max) {
            max = val;
        }
    }
    return max;
}

const numbers = [3, 7, 2, 8, 5];
const maxValue = findMaxValue(numbers); // 类型推断为 number | undefined

在上面的例子中,我们使用了 extends number 泛型约束,这意味着泛型类型必须是 number 类型或其子类型。

3. 泛型类

我们不仅可以在函数中使用泛型,还可以在类中使用泛型。例如,考虑一个简单的栈(堆栈)类:

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

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

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

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
const poppedNumber = numberStack.pop(); // 类型推断为 number | undefined

const stringStack = new Stack<string>();
stringStack.push('a');
stringStack.push('b');
stringStack.push('c');
const poppedString = stringStack.pop(); // 类型推断为 string | undefined

4. 在接口中使用泛型

泛型不仅可以在函数和类中使用,还可以在接口中使用。这使我们能够创建具有灵活类型的接口定义。例如,考虑一个简单的键值对接口:

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

const pair: KeyValuePair<string, number> = { key: 'age', value: 25 };

5. 默认泛型类型

在某些情况下,我们可能希望为泛型类型提供默认值。这可以通过在泛型声明中使用默认类型来实现:

function identity<T = any>(value: T): T {
    return value;
}

const result = identity(42); // 类型推断为 number

6. 结论

TypeScript 的泛型功能使得我们能够编写更加灵活、安全且可重用的代码。通过泛型,我们可以在编译时捕获许多类型错误,并在多种情况下实现通用的解决方案。无论是函数、类、接口还是类型约束,泛型都可以在各种场景中发挥重要作用。