TypeScript 中的 `infer` 和 `extends` 语法:类型系统的双剑客

409 阅读2分钟

TypeScript,这门优雅的编程语言,以其静态类型系统为 JavaScript 世界带来了秩序与和谐。在 TypeScript 的武器库中,inferextends 是两把锋利的剑,它们在类型的世界里舞动,为开发者提供了强大的类型推断和限制能力。

infer:类型推断的魔术师

infer 是 TypeScript 3.7 版本中引入的关键字,它在条件类型中扮演着魔术师的角色,能够巧妙地从泛型类型参数中变出一个更具体的类型。

使用场景:

  • 设计泛型函数时,希望它能够智能地根据传入的参数类型来返回一个相关的类型。
  • 在类型别名或接口中,基于已有的类型参数推导出新的类型。

如何使用:

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

function myFunction(): number {
  return 1;
}

type MyFunctionReturnType = ExtractReturnType<typeof myFunction>; // MyFunctionReturnType 被推断为 number

在这个例子中,ExtractReturnType 利用 infer 巧妙地推断出函数 myFunction 的返回类型。

extends:类型限制的守护者

extends 在 TypeScript 中扮演着守护者的角色,它确保类型参数符合我们的期望,不会偏离我们设定的轨迹。

使用场景:

  • 定义泛型函数或类时,希望限制传入的类型参数必须是某个类型或其子类型。
  • 确保一个类型是另一个类型的子集,维护类型的一致性和预期行为。

如何使用:

interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  department: string;
}

let employee: Employee = {
  name: "John Doe",
  age: 30,
  department: "Engineering"
};

在这个例子中,Employee 接口通过 extends 关键字继承了 Person 接口的属性,并添加了额外的属性。

何时使用 inferextends

  • 使用 infer 的情况通常涉及到泛型和复杂的类型操作,当你需要基于已有的复杂类型结构推导出新的类型时,infer 是一个非常有用的工具。
  • 使用 extends 的情况则是当你需要定义一个类型,这个类型是另一个类型的特定形式或扩展时。它常用于确保类型的兼容性和约束。

常见案例

  1. 泛型约束:使用 extends 来约束泛型参数,确保它们符合特定的结构或具有某些属性。
function log<T extends object>(item: T): void {
  console.log(item);
}
  1. 高级类型守卫:使用 infer 来创建更精确的类型守卫,这在处理复杂类型时非常有用。
type OptionalKeys<T> = {
  [K in keyof T]-?: undefined extends T[K] ? K : never;
}[keyof T];

interface Data {
  requiredField: string;
  optionalField?: number;
}

type Result = OptionalKeys<Data>; // "Result" 被推断为 "optionalField"

在上述案例中,OptionalKeys 利用 infer 和条件类型来推断出 Data 接口中所有的可选属性键。

通过合理地使用 inferextends,TypeScript 允许开发者编写出更加健壮和可维护的类型定义,从而提高代码的类型安全性。