【TypeScript】泛型函数详解

文章目录

    • 一、基本概念
      • 1. 什么是泛型?
      • 2. 基本泛型函数示例
      • 3. 类型推断
    • 二、使用多个泛型参数
    • 三、泛型约束
      • 1. 使用 extends 进行约束
      • 2. 约束的实际应用
    • 四、泛型函数中的常见错误
    • 五、泛型的应用场景
      • 1. 数据结构
      • 2. 函数组合
      • 3. 库和框架
    • 六、总结

在日常开发中,我们经常需要编写一些通用的函数,这些函数的输入与输出类型之间有某种联系,或者两个输入参数的类型相关联。为了更好地解决这些场景,TypeScript 提供了**泛型(Generic)**的功能,使得开发者可以编写类型更加灵活且安全的代码。本文将深入探讨 TypeScript 中的泛型函数及其相关的用法。

一、基本概念

1. 什么是泛型?

泛型是 TypeScript 中的一种工具,它允许我们在定义函数、接口或类时,不预先指定具体的类型,而是在使用时再传入具体的类型。这种特性使得代码更加灵活,并且可以处理多种类型的输入,而无需为每种类型重写相似的逻辑。

2. 基本泛型函数示例

考虑一个简单的函数,它返回数组的第一个元素:

function firstElement(arr: any[]) {
  return arr[0];
}

在上面的例子中,firstElement 函数能返回数组的第一个元素,但它的返回类型是 any,这意味着我们失去了类型的安全性。如果我们能让这个函数根据传入数组的类型来推断返回类型,将会更加安全和高效。此时,泛型就派上用场了。

我们可以通过在函数签名中声明一个类型参数来实现这一点:

function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}

在这个例子中,我们引入了一个名为 Type 的泛型,并在数组的类型和返回值中使用它。这就建立了输入(数组)和输出(返回值)之间的联系。现在,当我们调用这个函数时,TypeScript 会根据传入的数组类型推断出具体的返回类型:

// s 的类型为 'string'
const s = firstElement(["a", "b", "c"]);

// n 的类型为 'number'
const n = firstElement([1, 2, 3]);

// u 的类型为 undefined
const u = firstElement([]);

3. 类型推断

在上面的例子中,我们没有显式地指定 Type,因为 TypeScript 可以通过传入的数组自动推断出类型。这种自动推断特性让代码更加简洁,同时保证了类型的安全性。

二、使用多个泛型参数

泛型不仅可以处理单一类型,还可以处理多个类型。例如,我们可以编写一个通用的 map 函数,它接收一个输入数组和一个函数,并返回一个新的数组,数组中的元素是通过映射函数生成的:

function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
  return arr.map(func);
}

这个函数接受两个泛型参数 InputOutput,分别代表输入数组的元素类型和输出数组的元素类型。在调用时,TypeScript 会根据传入的参数自动推断这两个泛型的具体类型:

// n 的类型是 'string'
// parsed 的类型是 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

在这个例子中,TypeScript 推断 Inputstring 类型,因为传入的是一个字符串数组。而 Output 则被推断为 number,因为 map 函数中使用的回调函数 parseInt 返回了数字类型。

三、泛型约束

虽然泛型函数能够处理各种类型,但有时候我们希望限制泛型的类型范围。这种情况下,可以使用泛型约束。通过泛型约束,我们可以要求某个类型参数必须符合某些条件。

1. 使用 extends 进行约束

假设我们需要编写一个函数,它接受两个值并返回其中较长的一个。要做到这一点,函数的参数必须拥有 length 属性,因此我们需要通过 extends 关键字来约束类型:

function longest<Type extends { length: number }>(a: Type, b: Type): Type {
  return a.length >= b.length ? a : b;
}

在这个例子中,Type 被约束为必须拥有 length 属性。于是,我们可以传入数组或字符串等拥有 length 属性的值:

// longerArray 的类型是 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);

// longerString 的类型是 'alice' | 'bob'
const longerString = longest("alice", "bob");

// 错误!数字类型没有 'length' 属性
const notOK = longest(10, 100);

这里的 longest 函数通过泛型约束,只接受那些拥有 length 属性的参数。TypeScript 能够自动推断出返回值的类型,并在编译时防止无效的调用(例如传入没有 length 属性的数字类型)。

2. 约束的实际应用

泛型约束在实际开发中非常有用。例如,处理需要比较长度的对象、字符串或数组时,使用约束可以显著提高代码的安全性。

四、泛型函数中的常见错误

当我们使用泛型时,尤其是带有约束的泛型,有时可能会出现一些不容易发现的错误。以下是一个典型的错误示例:

function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type {
  if (obj.length >= minimum) {
    return obj;
  } else {
    return { length: minimum }; // 错误:返回的对象不匹配 Type 类型
  }
}

在这个例子中,虽然 Type 被约束为必须具有 length 属性,但函数的返回类型是 Type,这意味着函数承诺返回与传入参数相同类型的对象。然而,在 else 分支中返回的只是一个 { length: number } 的对象,它并不一定与传入的 Type 类型相符。

如果允许这种写法,可能会导致潜在的错误。例如,数组类型具有 slice 方法,而一个简单的 { length: number } 对象则没有这个方法:

// arr 得到 { length: 6 }
const arr = minimumLength([1, 2, 3], 6);

// 这里会因为 'slice' 方法不存在而出错
console.log(arr.slice(0));

为了避免这种错误,应该确保返回的对象完全匹配泛型参数的类型。

五、泛型的应用场景

泛型是 TypeScript 提供的强大工具,在以下几个场景中非常适用:

1. 数据结构

许多常见的数据结构(如数组、链表、树等)可以通过泛型来实现,以适应不同类型的数据。例如,可以编写一个通用的栈(stack)类:

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

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

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

在这个例子中,Stack 类可以适应任何类型的元素,而无需为每种类型单独定义一个栈类。

2. 函数组合

在函数式编程中,函数组合是一种常见的技术。通过泛型,可以创建能够组合不同类型函数的高阶函数。例如:

function compose<A, B, C>(f: (x: B) => C, g: (x: A) => B): (x: A) => C {
  return (x: A) => f(g(x));
}

这个函数可以组合两个具有不同类型签名的函数,返回一个新的组合函数。

3. 库和框架

TypeScript 的泛型在许多库和框架中得到了广泛应用。比如,在 React 中,Hooks(如 useState)就通过泛型来确保状态类型的安全性:

const [count, setCount] = useState<number>(0);

在这个例子中,useState 通过泛型确保了 count 的类型为 number,从而在后续操作中避免了类型错误。

六、总结

TypeScript 中的泛型为开发者提供了一种强大的工具,使得函数、类和接口能够处理多种类型,同时保持类型安全。通过泛型,我们能够编写出更加灵活且通用的代码,避免重复编写相似的逻辑。与此同时,泛型约束允许我们在确保灵活性的同时,限制某些类型的使用范围,避免潜在的运行时错误。

推荐:

  • JavaScript
  • react
  • vue

在这里插入图片描述

你可能感兴趣的:(#,TypeScript,typescript,javascript,前端,node.js,ecmascript)