Skip to content

映射类型 (Mapped Types)

映射类型是 TypeScript 中一种强大的类型转换工具,它允许你基于现有类型创建新的类型。通过遍历一个类型的属性并对其应用某种变换,你可以轻松地创建出具有相似结构但不同属性类型的对象类型。映射类型常用于库设计、工具函数以及需要对已有类型进行细微调整的场景。

1. 基本概念

映射类型的基本语法如下:

typescript
type MappedType = {
  [P in Keys]: Transformation;
};
  • Keys 是一个联合类型,表示要遍历的属性名。
  • Transformation 是对每个属性应用的类型转换表达式,可以包含属性 P

2. 使用场景

  • 修改属性的可选性:将所有属性变为可选或必填。
  • 改变属性的只读性:将所有属性变为只读或可写。
  • 转换属性的类型:例如,将所有属性变为它们的包装类型(如 string 变为 String)。
  • 添加修饰符:例如,为所有属性添加 readonly?(可选)修饰符。

3. 映射类型中的 +- 修饰符

在 TypeScript 的映射类型中,+- 修饰符用于控制属性的可选性(?)和只读性(readonly)。这些修饰符允许你在创建新类型时精确地调整属性的行为。通过添加或移除这些修饰符,你可以灵活地转换已有类型的属性特性。

1. 修饰符的作用

  • + 修饰符:确保属性是必填或只读的。
  • - 修饰符:确保属性是可选或可写的。

2. 具体应用

2.1 控制属性的可选性

  • +?:使属性变为必填(移除可选性)。
  • -?:使属性变为可选(添加可选性)。

2.2 控制属性的只读性

  • +readonly:使属性变为只读(添加只读性)。
  • -readonly:使属性变为可写(移除只读性)。

3. 示例解析

示例 1: 控制属性的可选性

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

// 将所有可选属性变为必填
type RequiredPerson = {
  [P in keyof Person]-?: Person[P];
};

// 将所有必填属性变为可选
type OptionalPerson = {
  [P in keyof Person]+?: Person[P];
};

在这个例子中:

  • RequiredPersonPerson 中的所有可选属性(如 age)变为必填。
  • OptionalPersonPerson 中的所有必填属性(如 name)变为可选。

示例 2: 控制属性的只读性

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

// 将所有属性变为只读
type ReadonlyPerson = {
  readonly [P in keyof Person]+?: Person[P];
};

// 将所有属性变为可写
type WritablePerson = {
  -readonly [P in keyof Person]-?: Person[P];
};

在这个例子中:

  • ReadonlyPersonPerson 中的所有属性变为只读(包括原本就只读的 name 和原本可写的 age)。
  • WritablePersonPerson 中的所有属性变为可写(包括原本只读的 name 和原本可写的 age)。

4. 示例解析

示例 1: 修改属性的可选性

typescript
type PartialPerson = {
  [P in keyof Person]?: Person[P];
};

在这个例子中,PartialPersonPerson 类型的所有属性变为可选。

示例 2: 改变属性的只读性

typescript
type ReadonlyPerson = {
  readonly [P in keyof Person]: Person[P];
};

在这个例子中,ReadonlyPersonPerson 类型的所有属性变为只读。

示例 3: 转换属性的类型

typescript
type Stringify<T> = {
  [P in keyof T]: string;
};

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

type StringifiedPerson = Stringify<Person>; // { name: string, age: string }

在这个例子中,Stringify<Person>Person 类型的所有属性值都转换为了 string 类型。

示例 4: 添加修饰符

typescript
type WithQuestionMark<T> = {
  [P in keyof T]+?: T[P];
};

type WithReadOnly<T> = {
  readonly [P in keyof T]: T[P];
};

在这个例子中,WithQuestionMarkWithReadOnly 分别为所有属性添加了 ?(可选)和 readonly 修饰符。

5. 内置映射类型

TypeScript 提供了一些常用的内置映射类型,这些类型可以帮助简化常见的类型操作:

  • Partial<T>:使 T 的所有属性变为可选。
  • Readonly<T>:使 T 的所有属性变为只读。
  • Record<K, T>:构建一个新的对象类型,其中键来自 K,值来自 T
  • Pick<T, K>:从 T 中挑选出由 K 指定的属性。
  • Exclude<T, U>:从 T 中排除所有可以赋值给 U 的类型。
  • Extract<T, U>:从 T 中提取出所有可以赋值给 U 的类型。

示例:使用内置映射类型

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

type PartialPerson = Partial<Person>; // { name?: string; age?: number; }

type ReadonlyPerson = Readonly<Person>; // { readonly name: string; readonly age: number; }

type StringKeyedObject = Record<string, any>; // { [key: string]: any; }

type NameOnly = Pick<Person, "name">; // { name: string; }

6. 条件类型与映射类型的结合

你可以将条件类型与映射类型结合使用,以实现更复杂的类型转换逻辑。

示例:根据属性值类型有条件地转换属性

typescript
type TransformProps<T> = {
  [P in keyof T]: T[P] extends string ? number : T[P];
};

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

type TransformedPerson = TransformProps<Person>; // { name: number; age: number; }

在这个例子中,TransformProps<Person>Person 类型中的所有 string 类型属性转换为 number 类型。

7. 注意事项

  • 类型安全:映射类型提供了强大的类型转换能力,但在使用时应确保不会破坏类型的安全性。

  • 性能考虑:复杂类型的映射可能会增加编译时间,对于大型项目应权衡其利弊。

  • 意图清晰:在设计映射类型时,尽量保持意图清晰,避免过度复杂的类型操作,提高代码的可读性和维护性。

总结

映射类型是 TypeScript 中一个非常有用的特性,它使得你可以基于现有类型创建新的类型,并对属性进行各种转换。通过合理使用映射类型,你可以编写出更加灵活且类型安全的代码。

Released under the MIT License.