什么是泛型?

关于什么是泛型这个问题不是太好回答,比如在面试中,如果有候选人反过来问我这个问题,可能我也给不出一个特别标准的答案。

不过,我们可以借用 Java 中泛型的释义来回答这个问题:泛型指的是类型参数化,即将原来某种具体的类型进行参数化。和定义函数参数一样,我们可以给泛型定义若干个类型参数,并在调用时给泛型传入明确的类型参数。设计泛型的目的在于有效约束类型成员之间的关系,比如函数参数和返回值、类或者接口成员和方法之间的关系。

泛型类型参数

  1. function reflect(param: unknown) {
  2. return param;
  3. }
  4. const str = reflect('string'); // str 类型是 unknown
  5. const num = reflect(1); // num 类型 unknown

reflect 函数虽然可以接收一个任意类型的参数并原封不动地返回参数的值,不过返回值类型不符合我们的预期。
我们可以通过尖括号 <> 语法给函数定义一个泛型参数 P,并指定 param 参数的类型为 P ,如下代码所示:

  1. function reflect<P>(param: P) {
  2. return param;
  3. }
  4. const reflectStr = reflect<string>('string'); // reflectStr 类型是 string
  5. const reflectNum = reflect<number>(1); // reflectNum 类型 number

泛型不仅可以约束函数整个参数的类型,还可以约束参数属性、成员的类型,比如参数的类型可以是数组、对象,如下示例:

  1. function reflectArray<P>(param: P[]) {
  2. return param;
  3. }
  4. const reflectArr = reflectArray([1, '1']); // reflectArr 是 (string | number)[]

extends

typescript 2.8引入了条件类型关键字: extends,长这个样子:

  1. T extends U ? X : Y

用大白话可以表示为:

如果T包含的类型 是 U包含的类型的 ‘子集’,那么取结果X,否则取结果Y。

再举几个ts预定义条件类型的例子,加深理解:

  1. type NonNullable<T> = T extends null | undefined ? never : T;
  2. // 如果泛型参数 T 为 null 或 undefined,那么取 never,否则直接返回T。
  3. let demo1: NonNullable<number>; // => number
  4. let demo2: NonNullable<string>; // => string
  5. let demo3: NonNullable<undefined | null>; // => never

分配式extends

  1. T extends U ? X : Y

其实就是当上面的T为联合类型的时候,会进行拆分,有点类似数学中的分解因式:

(a + b) * c => ac + bc

再举个官网的例子:

  1. type Diff<T, U> = T extends U ? never : T; // 找出T的差集
  2. type Filter<T, U> = T extends U ? T : never; // 找出交集
  3. type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // => "b" | "d"
  4. // <"a" | "b" | "c" | "d", "a" | "c" | "f">
  5. // 相当于
  6. // <'a', "a" | "c" | "f"> |
  7. // <'b', "a" | "c" | "f"> |
  8. // <'c', "a" | "c" | "f"> |
  9. // <'d', "a" | "c" | "f">
  10. type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // => "a" | "c"
  11. // <"a" | "b" | "c" | "d", "a" | "c" | "f"> 同上
  12. let demo1: Diff<number, string>; // => number

infer

Infer 关键字用于条件中的类型推导。
Typescript 官网也拿 ReturnType 这一经典例子说明它的作用:

  1. type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

理解为:如果 T 继承了 extends (…args: any[]) => any 类型,则返回类型 R,否则返回 any。其中 R 是什么呢?R 被定义在 extends (…args: any[]) => infer R 中,即 R 是从传入参数类型中推导出来的。

  1. /* _____________ 你的代码 _____________ */
  2. type First<T extends any[]> = T extends [infer K, ...infer S] ? K : never
  3. /* _____________ 测试用例 _____________ */
  4. import type { Equal, Expect } from '@type-challenges/utils'
  5. type cases = [
  6. Expect<Equal<First<[3, 2, 1]>, 3>>,
  7. Expect<Equal<First<[() => 123, { a: string }]>, () => 123>>,
  8. Expect<Equal<First<[]>, never>>,
  9. Expect<Equal<First<[undefined]>, undefined>>,
  10. ]
  11. type errors = [
  12. // @ts-expect-error
  13. First<'notArray'>,
  14. // @ts-expect-error
  15. First<{ 0: 'arrayLike' }>,
  16. ]