TypeScript高级类型:探索Mapped Types的威力及衍生

42 阅读5分钟

TypeScript是一种强类型的编程语言,它在JavaScript的基础上添加了静态类型检查。除了基本的类型系统外,TypeScript还提供了许多高级类型特性,其中之一就是Mapped Types(映射类型)。Mapped Types是一种强大的工具,可以根据现有类型创建新的类型。

Mapped Types

Mapped Types的威力在于它们允许我们对现有类型进行转换修改,从而创建出更具表现力和灵活性的类型。通过使用Mapped Types,我们可以轻松地进行属性的重命名、添加或删除属性、将属性设置为可选或只读,甚至可以根据现有属性的类型创建新的属性。

一个常见的用例是将现有类型的所有属性设置为只读。通过使用Readonly Mapped Type,我们可以轻松地实现这一目标。例如,假设我们有一个名为Person的接口,其中包含nameage属性。我们可以使用Readonly Mapped Type将Person接口中的所有属性设置为只读

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

type ReadonlyPerson = Readonly<Person>;

const person: ReadonlyPerson = {
  name: "John",
  age: 30
};

person.name = "Jane"; // Error: Cannot assign to 'name' because it is a read-only property.

除了Readonly Mapped Type外,TypeScript还提供了许多其他的Mapped Types,如PartialRequiredPick等。这些Mapped Types可以根据我们的需求对现有类型进行灵活的转换和修改。

另一个强大的特性是使用Mapped Types进行属性的重命名。通过使用Record Mapped Type,我们可以将一个类型中的属性重命名为另一个类型中的属性。例如,假设我们有一个名为User的接口,其中包含usernameemail属性。我们可以使用Record Mapped Type将User接口中的username属性重命名为name

interface User {
  username: string;
  email: string;
}

type RenamedUser = Record<"name", User["username"]> & Omit<User, "username">;

const user: RenamedUser = {
  name: "John",
  email: "john@example.com"
};

console.log(user.name); // Output: "John"
console.log(user.email); // Output: "john@example.com"

总结:

本文介绍了TypeScript中的Mapped Types,探索了它们的威力和实用性。Mapped Types允许我们根据现有类型创建新的类型,并进行属性的转换和修改。通过使用Mapped Types,我们可以轻松地实现属性的重命名、添加或删除属性、将属性设置为只读或可选等操作。掌握Mapped Types将使我们的代码更具表现力和灵活性,提高代码的可维护性和可扩展性。

衍生知识点

当涉及到Mapped Types时,其实还有其他较多的类似知识点,我们可以稍微提及一下。

  1. 条件类型(Conditional Types):条件类型是TypeScript中另一个强大的类型工具,它允许我们根据条件选择不同的类型。条件类型通常与Mapped Types结合使用,以根据某些条件转换或修改类型。通过使用条件类型,我们可以实现更复杂的类型转换和操作。
  2. keyof 操作符:keyof 操作符用于获取一个类型的所有属性名,它返回一个由属性名组成的联合类型。在Mapped Types中,我们经常使用keyof操作符来遍历和操作类型的属性。 keyof
interface Person {
  name: string;
  age: number;
  address: string;
}

type PersonKeys = keyof Person; // "name" | "age" | "address"

function getProperty(obj: Person, key: keyof Person) {
  return obj[key];
}

const person: Person = {
  name: "John",
  age: 30,
  address: "123 Main St"
};

console.log(getProperty(person, "name")); // Output: "John"
console.log(getProperty(person, "age")); // Output: 30
console.log(getProperty(person, "address")); // Output: "123 Main St"

在上面的示例中,我们使用keyof操作符获取了Person接口的所有属性名,并将其存储在PersonKeys类型中。然后,我们定义了一个名为getProperty的函数,它接受一个对象和一个属性名作为参数,并返回该属性的值。

  1. infer 关键字:infer 关键字用于在条件类型中推断类型变量。它允许我们从一个类型中提取出另一个类型,并将其用于条件类型的结果。infer 关键字通常与条件类型和Mapped Types一起使用,以实现类型的推断和转换。

infer关键字示例

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

function sum(a: number, b: number): number {
  return a + b;
}

type SumReturnType = ReturnType<typeof sum>; // number

console.log(typeof sum(2, 3)); // Output: "number"

在上面的示例中,我们定义了一个名为ReturnType的类型,它接受一个函数类型T作为参数。我们使用条件类型和infer关键字来推断函数的返回类型。然后,我们定义了一个名为sum的函数,并使用SumReturnType类型来推断sum函数的返回类型。

  1. 索引类型(Index Types):索引类型允许我们通过索引访问类型的属性。在Mapped Types中,我们可以使用索引类型来动态地访问和操作类型的属性。

索引类型示例

interface Dictionary<T> {
  [key: string]: T;
}

const dictionary: Dictionary<number> = {
  a: 1,
  b: 2,
  c: 3
};

console.log(dictionary["a"]); // Output: 1
console.log(dictionary["b"]); // Output: 2
console.log(dictionary["c"]); // Output: 3

我们定义了一个名为Dictionary的接口,它使用索引类型来定义一个字符串索引和对应的值类型。然后,我们创建了一个名为dictionary的对象,它的键是字符串,值是数字类型。我们可以使用索引访问运算符来访问和操作dictionary对象的属性。

  1. 映射类型的递归和嵌套:Mapped Types可以嵌套和递归使用,以创建更复杂的类型转换和操作。通过嵌套和递归使用Mapped Types,我们可以实现更高级的类型转换和修改。

本文同步我的技术文档