构建 Typescript 知识体系(十)-高级类型之条件类型

415 阅读4分钟

这是我参与更文挑战的第十六天,活动详情查看:更文挑战

条件类型是什么

一种由条件表达式所决定的类型, 表现形式为 T extends U ? X : Y , 即如果类型 T 可以被赋值给类型 U,那么结果类型就是 X 类型,否则为 Y 类型。

条件类型使类型具有了不唯一性,增加了语言的灵活性,

多个条件类型的嵌套

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

以上代码是多个条件类型的嵌套,会一次判断 T 的类型,然后返回不同的字符串。

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

// 类型推断为一个字面量类型:  type T1 = "string"
type T1 = TypeName<string>;
// 类型推断为  type T2 = "object"
type T2 = TypeName<string[]>;

分布式条件类型

T extends U ? X : Y 如果类型 T 是一个联合类型, 如: (A | B) extends U ? X : Y

这个时候的结果类型,会变成多个条件类型的联合类型,

可以拆解为 (A extends U ? X :Y)| ``(B extends U ? X :Y)

TypeName

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

// 类型推断为string和object的字面量联合类型 --- type T3 = "string" | "object"
type T3 = TypeName<string | string[]>;

Diff<T,U> 等价于 Exclude<T,U>

利用此特性可以帮助我们实现一些类型的过滤。

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

type Diff<T, U> = T extends U ? never : T;
/*
类型推断为  type T4 = "b" | "c"
*/
type T4 = Diff<"a" | "b" | "c", "a" | "e">;

首先会被拆解为多个条件类型的联合类型

Diff<'a','a' | 'e'> | Diff<'b' ,'a' | 'e'> | Diff<'c' ,'a'|'e'>

  • a是否可以被赋值给字面量联合类型,  -可以,则返回never
  • b是否可以被赋值给字面量联合类型,  -不可以,则返回 b
  • c是否可以被赋值给字面量联合类型,  -不可以,则返回 b

结果为: never | b | c

最后neverb,c的联合类型,因此返回 b| c

可以从类型T中过滤掉,可以赋值给类型U的类型,

我们可以基于Diff再做扩展,从类型中除去一些不需要的类型,如 undefinednull

NotNull 等价于 NoneNullable<T,U>

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";

type Diff<T, U> = T extends U ? never : T;

type NotNull<T> = Diff<T, undefined | null>;
/*
类型推断为  type T5 = string | number
*/
type T5 = NotNull<string | number | undefined | null>;

实际上,上面两个类型,官方已经有实现了,

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";
// 自定义类型
type Diff<T, U> = T extends U ? never : T;
// 自定义类型
type NotNull<T> = Diff<T, undefined | null>;

/*
从类型T中过滤掉可以赋值给类型U的类型
Exclude<T,U>  等同于  Diff<T, U>

NoneNullable<T,U>  等同于  NotNull<T>

从类型T中抽取出可以赋值给类型U的类型
Extract<T,U>  与  Exclude<T,U> 相反
*/

// 类型推断为 type T6 = "a"
type T6 = Extract<"a" | "b" | "c", "a" | "e">;

ReturnType<()=>{}>

// 可以获取一个函数返回值的类型
// 类型推断为  type T7 = string
type T7 = ReturnType<() => string>;

源码及实现原理

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;

参数 T 可以被赋值给一个函数,此函数有任意的参数,返回值类型也是任意的,

由于函数的返回值类型是不确定的,所以使用了一个 infer 关键字,表示延迟推断,需要根据实际的情况来确定,

如果实际的情况是返回 类型 R,那么结果类型就是 R,否则返回值类型就是any

ReturnType 接受一个函数类型,并返回这个函数的返回值类型。这里关键在于 (infer R),返回值类型 R 此时是不能确定的,只有在函

数执行之后才知道,是一种延迟推断,所以用 infer 修饰。特殊情况,比如 type T1 = ReturnType<any>T1 的类型是 any

typeinterface 多数情况下有相同的功能,就是定义类型。但有一些小区别:

type:不是创建新的类型,只是为一个给定的类型起一个名字。type 还可以进行联合、交叉等操作,引用起来更简洁。

interface:创建新的类型,接口之间还可以继承、声明合并。