映射类型

映射类型可以从旧类型创建新类型,通过遍历类型的属性来转换类型。映射类型是 TypeScript 中强大的类型转换工具。

基本语法

映射类型使用 in 关键字遍历类型的属性。

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

interface User {
  id: number
  name: string
}

type ReadonlyUser = Readonly<User>

const user: ReadonlyUser = {
  id: 1,
  name: "张三"
}

[P in keyof T] 遍历 T 的所有属性,readonly 修饰符使属性只读。

内置映射类型

TypeScript 提供了一些内置的映射类型。

interface User {
  id: number
  name: string
  email: string
}

type PartialUser = Partial<User>
type RequiredUser = Required<User>
type ReadonlyUser = Readonly<User>
type UserKeys = keyof User
type UserPick = Pick<User, "id" | "name">
type UserOmit = Omit<User, "email">
type UserRecord = Record<"a" | "b", User>

const partialUser: PartialUser = { name: "张三" }
const readonlyUser: ReadonlyUser = { id: 1, name: "张三", email: "test@example.com" }
const pickedUser: UserPick = { id: 1, name: "张三" }
const omittedUser: UserOmit = { id: 1, name: "张三" }
  • Partial<T>:所有属性变为可选
  • Required<T>:所有属性变为必需
  • Readonly<T>:所有属性变为只读
  • Pick<T, K>:选择指定的属性
  • Omit<T, K>:排除指定的属性
  • Record<K, T>:创建键为 K、值为 T 的类型

属性修饰符

可以使用 +- 添加或移除修饰符。

interface User {
  readonly id: number
  name?: string
}

type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}

type RequiredUser<T> = {
  [P in keyof T]-?: T[P]
}

type MutableUser = Mutable<User>
type RequiredUserType = RequiredUser<User>

const mutableUser: MutableUser = {
  id: 1,
  name: "张三"
}

mutableUser.id = 2

-readonly 移除只读修饰符,-? 移除可选修饰符。

自定义映射类型

可以创建自定义的映射类型。

type Nullable<T> = {
  [P in keyof T]: T[P] | null
}

interface User {
  id: number
  name: string
}

type NullableUser = Nullable<User>

const user: NullableUser = {
  id: 1,
  name: null
}

Nullable<T> 将所有属性类型变为可空。

映射类型与条件类型

映射类型可以与条件类型结合使用。

type NonNullableProperties<T> = {
  [P in keyof T]: NonNullable<T[P]>
}

interface User {
  id: number | null
  name: string | null
  email: string
}

type NonNullableUser = NonNullableProperties<User>

const user: NonNullableUser = {
  id: 1,
  name: "张三",
  email: "test@example.com"
}

NonNullableProperties<T> 将所有属性的非空类型提取出来。

键重映射

TypeScript 4.1 引入了键重映射,使用 as 子句。

type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
}

interface User {
  id: number
  name: string
}

type UserGetters = Getters<User>

const getters: UserGetters = {
  getId: () => 1,
  getName: () => "张三"
}

as 子句可以重命名键,Capitalize 是内置的字符串操作类型。

过滤属性

键重映射可以过滤属性。

type OnlyString<T> = {
  [P in keyof T as T[P] extends string ? P : never]: T[P]
}

interface User {
  id: number
  name: string
  email: string
  age: number
}

type StringProperties = OnlyString<User>

const strings: StringProperties = {
  name: "张三",
  email: "test@example.com"
}

never 类型会从联合类型中排除,实现属性过滤。

实际应用

映射类型在实际开发中常用于创建表单类型。

interface Entity {
  id: number
  name: string
  email: string
  createdAt: Date
}

type Form<T> = {
  [P in keyof T]?: T[P] | string
}

type EntityForm = Form<Entity>

const form: EntityForm = {
  id: "1",
  name: "张三",
  email: "test@example.com"
}

function validateForm<T>(form: Form<T>): Partial<T> {
  return Object.entries(form).reduce((acc, [key, value]) => {
    if (value !== undefined) {
      acc[key as keyof T] = value as T[keyof T]
    }
    return acc
  }, {} as Partial<T>)
}

Form<T> 将所有属性变为可选,并允许字符串值,适用于表单处理。

小结

映射类型从旧类型创建新类型,通过遍历属性来转换类型。TypeScript 提供了 PartialRequiredReadonlyPickOmitRecord 等内置映射类型。可以使用 +- 添加或移除修饰符。键重映射可以重命名和过滤属性。映射类型在实际开发中常用于创建表单类型和数据转换类型。