TypeScript 中的泛型 (Generic) 详解

阅读时长 8 分钟读完

在 TypeScript 中,泛型 (Generic) 是一种非常重要且强大的特性。它可以让我们编写更加通用、灵活且可复用的代码,不仅可以提高代码的可靠性,还可以节省大量的重复工作。本文将深入介绍 TypeScript 中的泛型,涵盖其基本语法、使用场景和实际应用示例。

泛型的基本语法

泛型在 TypeScript 中的语法很简单,就是在函数、类、接口等定义中使用尖括号 <> 包裹一个或多个泛型参数,并在函数体或类中使用泛型参数。下面是一个简单的泛型函数示例:

在这个示例中,identity 函数使用泛型参数 T 来描述其输入参数类型和返回值类型。这个泛型参数可以在函数中的任何位置使用,包括函数参数、返回值、函数体中的变量等。

调用 identity 函数时,可以显式指定泛型参数类型,也可以从输入参数类型自动推到泛型参数类型:

在这个示例中,我们分别指定了 str 参数为 string 类型,和 num 参数为 number 类型。如果我们没有显式指定泛型参数类型,TypeScript 将自动根据输入参数类型推导出泛型参数类型。

泛型的使用场景

泛型在 TypeScript 中有很多用途,以下是一些常见的使用场景:

1. 函数参数类型灵活

泛型可以让函数的输入参数类型更灵活,同时保证输出值类型与输入值类型一致。例如,可以使用以下函数将任意数组中的第一个元素包装到一个新的数组中:

在这个示例中,wrap 函数接受一个泛型数组 T[] 作为输入参数,可以确保输入数组的元素类型与输出数组的元素类型一致,并且输出数组的类型也是泛型数组类型。

2. 类型约束和泛型约束

泛型可以通过约束输入参数类型,使其满足某些特定条件,例如:

-- -------------------- ---- -------
--------- ---------- -
  ------- -------
-

-------- ------------- ------- ---------------- --- ---- -
  ------------------------
-

--------------------- -- -- -
------------- ------- - --- -- -- -
----------------- -- --------- ------ --
展开代码

在这个示例中,Lengthwise 接口定义了一个 length 属性,然后使用泛型约束 T extends Lengthwise 来指定函数的输入参数类型必须满足 Lengthwise 接口的约束条件。

3. 泛型类

泛型类可以与泛型函数类似,使用泛型参数来描述类中的一些数据类型和方法。例如:

-- -------------------- ---- -------
----- -------- -
  ------ --- - ---
  ---------- -- -
    ----------------------
  -
  ------ - - --------- -
    ------ -----------------
  -
-

--- ----- - --- ----------------
--------------
--------------
------------------------- -- -- -
------------------------- -- -- -
展开代码

在这个示例中,Stack 类使用泛型参数 T 来描述数据类型,同时定义了 pushpop 方法来实现栈的基本操作。

4. 泛型接口

泛型接口是一种定义泛型类型的方式,用来描述一些通用的数据格式和方法接口。例如:

-- -------------------- ---- -------
--------- ------ -
  ------ --
  --------- --
-

--- ---- ----------- - -
  ------ --------
  -------- -
    ------ -----------
  -
--

-------------------------- -- -- -------
展开代码

在这个示例中,Box 接口使用泛型参数 T 来描述数据类型,同时定义了 value 属性和 unwrap 方法,用来包装和解包值。

泛型的实际应用

泛型在实际开发中有很多重要的用途,以下是一些常见的应用示例:

1. lodash 函数库

lodash 是一个流行的 JavaScript 函数库,其中涵盖了大量的泛型函数和泛型接口,可以大大提高代码的可复用性和灵活性。

例如,以下是 lodash 中的一个泛型函数 compact,用来过滤掉数组中的 falsy 值:

在这个示例中,compact 函数使用泛型参数 T 来描述数组中的元素类型。然后使用 Array.filter 函数和 Boolean 函数来过滤掉数组中的 falsy 值。

2. React 组件和高阶组件

React 中经常使用泛型接口和泛型参数来描述组件的输入和输出类型,特别是在编写高阶组件时。例如:

-- -------------------- ---- -------
--------- ---------------- -
  ---------- --------
-

-------- ------------- ------- ------------------
  ----------------- ----------------------
- -
  ------ -------- ----------------------- -- -
    -- ----------------- -
      ------ ----------------------
    - ---- -
      ------ ----------------- ---------- ---
    -
  --
-

-------- ------------------ - ----- ------- ---- ------- ---------- ------- -- -
  ------ -
    -----
      ----- ------------- ---- -----------
    ------
  --
-

--- ------------------ - -------------------------

------------------- ------------ -------- ----------------- ---
展开代码

在这个示例中,我们定义了一个泛型函数 withLoading,它接受一个输入参数 WrappedComponent: React.ComponentType<P>,返回一个新的组件 LoadingComponent,并使用 isLoading 属性来控制加载状态。使用泛型约束 P extends WithLoadingProps 来指定组件的输入类型必须满足 WithLoadingProps 接口的约束条件,然后使用 {...props} 将输入参数传递给 WrappedComponent 组件。

3. Redux 中间件

Redux 是一个非常流行的 JavaScript 状态管理库,其中使用了大量的泛型接口和泛型参数来描述数据结构和中间件的类型。

例如,以下是 Redux 中间件的类型定义,其中使用了泛型参数 S 来描述状态类型,A 来描述 action 类型,以及 DispatchGetStateMiddlewareAPI 泛型接口来描述中间件的基本方法:

-- -------------------- ---- -------
--------- ---------------- - ------- ------- -
  --------- ------------
  ----------- --
-

--------- ---------- ------- ------ - ---------- -
  -- ------- ---------- --- --
-

--------- ----------- -
  --- --
  ------------- ------- -- -- --- --
-

------ --------- -----------
  - - ----
  - ------- ------ - ----------
  - ------- ----------- - -----------
- -
  ----- ---------------- ---- ------ ------------ -- -------- -- -- ----
-
展开代码

在这个示例中,我们定义了一个泛型接口 Middleware 来描述 Redux 中间件的基本结构,使用泛型参数 S 来描述状态类型,A 来描述 action 类型,以及 D 来描述 dispatch 类型。然后使用 MiddlewareAPIDispatchGetState 泛型接口来定义相应的函数类型和方法。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d6727da941bf7134c36b62

纠错
反馈

纠错反馈