前端实践选题-泛型 | 豆包MarsCode AI刷题

65 阅读4分钟

TypeScirpt类、泛型的使用实践记录:探讨TypeScript中的泛型的使用方法和场景,以及如何使用类型约束来增加代码的灵活性和安全性


在 TypeScript 中, 泛型(Generics) 是一种强大的工具,用于提高代码的灵活性和可复用性,同时确保类型安全。

泛型 允许我们在定义函数、类或接口时,指定类型参数,而不是写死类型,这样在使用时可以根据具体情况传入不同的类型,从而避免重复的代码并增加灵活性。

1. 泛型的基本用法

泛型的基本语法是通过 尖括号(<>)定义一个类型参数,在函数、接口或类中使用这些类型参数。

示例:泛型函数

// 泛型函数
function identity<T>(value: T): T {
  return value;
}

let num = identity(42);  // num 的类型为 number
let str = identity("Hello");  // str 的类型为 string

在这个例子中,<T> 是一个类型参数,可以代表任何类型。当调用 identity 函数时,TypeScript 会推断出传入值的类型,并将其作为 T 来使用。

示例:泛型接口

interface Box<T> {
  value: T;
}

let numberBox: Box<number> = { value: 42 };
let stringBox: Box<string> = { value: "Hello" };

这里,Box 是一个泛型接口,它定义了一个 value 属性,类型为 T,可以根据需要创建不同类型的 Box

2. 泛型在类中的使用

泛型不仅可以用于 函数接口,也可以用于

class GenericStack<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];
  }
}

const numberStack = new GenericStack<number>();
numberStack.push(10);
numberStack.push(20);

const stringStack = new GenericStack<string>();
stringStack.push("Hello");
stringStack.push("World");

在这个例子中,GenericStack 类是一个泛型类,它的 pushpop 方法都可以接受任何类型的元素。

3. 泛型约束(Type Constraints)

类型约束:有时候我们希望将泛型的类型参数限制为某些类型(例如,限制为实现了某个接口的类型)。这时可以使用 类型约束

示例:限制泛型类型

interface Lengthwise {
  length: number;
}

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

logLength([1, 2, 3]);  // 数组具有 length 属性
logLength("Hello");  // 字符串具有 length 属性
logLength({ length: 10 });  // 自定义对象,具有 length 属性

// 下面的会报错,因为数字类型没有 length 属性
// logLength(42);

在这个例子中,T extends Lengthwise 表示类型 T 必须包含 length 属性。通过使用泛型约束,我们保证了在 logLength 函数中调用 .length 时,它确实是存在的。

4. 默认泛型类型

TypeScript 允许为泛型提供默认类型。这意味着如果调用函数或类时没有传入具体类型,TypeScript 会使用默认类型。

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

let arr1 = createArray(3, 42);  // T 默认是 number
let arr2 = createArray<string>(3, "Hello");  // T 被显式指定为 string

在这个例子中,function createArray<T = number>(length: number, value: T): 表示类型 T 默认类型为 number类型。

5. 多个泛型参数

TypeScript 允许一个函数、类或接口使用多个泛型参数。

function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}

let result = swap([1, "one"]);  // result 的类型是 [string, number]

6. 泛型与联合类型

泛型也可以与联合类型一起使用,这使得代码更具灵活性。

function combine<T, U>(a: T, b: U): T | U {
  return Math.random() > 0.5 ? a : b;
}

let result = combine(10, "Hello");  // result 的类型是 number | string

7. 泛型在高阶函数中的应用

泛型特别适用于高阶函数,这些函数接收另一个函数作为参数返回另一个函数。使用泛型时,我们可以确保类型的传递和返回的一致性。

function withLogging<T>(fn: (...args: any[]) => T): (...args: any[]) => T {
  return function (...args: any[]) {
    console.log("Arguments:", args);
    return fn(...args);
  };
}

const add = (a: number, b: number): number => a + b;
const loggedAdd = withLogging(add);

loggedAdd(1, 2);  // 输出: Arguments: [1, 2]

8. 泛型的应用场景

  1. 集合类和容器类:如数组、链表、栈、队列等容器类型,可以使用泛型来确保元素的类型一致性。
  2. 工具函数:在处理不同类型的数据时,使用泛型函数可以避免重复编写类似功能的代码,增加代码复用性。
  3. API 返回类型:根据不同的请求,API 可以返回不同类型的数据,通过泛型确保返回的数据类型与请求匹配。
  4. React 组件:React 中的组件(尤其是函数组件)可以使用泛型来接受不同类型的 props。

总结

  • 通过使用泛型,TypeScript 使得代码更加灵活和可复用
  • 使用类型约束可以保证代码的类型安全,避免类型错误。
  • 泛型使得类型推导更加精确,避免了硬编码的类型,降低了出错的几率。

泛型的使用不仅限于函数,还可以扩展到接口类型别名等,提供了丰富的应用场景来提升代码的可维护性和安全性。