Featured image of post 使用映射类型动态转换类型Featured image of post 使用映射类型动态转换类型

使用映射类型动态转换类型

通过映射现有接口的键、利用键重新映射和修饰符来制作动态类型。

介绍

映射类型允许您通过迭代其键来转换现有对象类型。将它们视为 Array.prototype.map() 但处于 类型级别。语法 { [K in keyof T]: T[K] } 通过对 T 的每个属性应用转换来创建新类型。这就是 TypeScript 的许多内置实用程序类型在幕后工作的方式。


基本映射类型

最简单的映射类型创建现有类型的克隆:

type Clone<T> = { [K in keyof T]: T[K] };

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

type UserClone = Clone<User>;
// { name: string; age: number }

添加修饰符

映射类型可以应用三个修饰符:readonly?(可选)和 -(删除):

// Make all properties readonly
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

// Make all properties optional
type MyPartial<T> = { [K in keyof T]?: T[K] };

// Remove readonly (the - modifier)
type MyMutable<T> = { -readonly [K in keyof T]: T[K] };

// Remove optional
type MyRequired<T> = { [K in keyof T]-?: T[K] };

这些正是内置 Readonly<T>Partial<T>Required<T> 的实现方式。


使用 as 键重新映射进行过滤

TypeScript 4.1 引入了使用 as 子句的键重新映射。这允许过滤或重命名键:

// Filter keys that are strings
type StringKeys<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

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

type OnlyStrings = StringKeys<User>;
// { name: string; email: string }

模板文字关键更改

将映射类型与 模板文字类型 结合起来以转换键名称:

// Add a prefix to all keys
type WithPrefix<T, P extends string> = {
  [K in keyof T as `${P}_${K & string}`]: T[K];
};

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

type Prefixed = WithPrefix<User, 'user'>;
// { user_name: string; user_age: number }

转换为 getter/setter 约定:

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

type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }

选择和省略实施

以下是 PickOmit 的内部工作原理:

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type MyOmit<T, K extends keyof any> = {
  [P in keyof T as P extends K ? never : P]: T[P];
};

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

type NameOnly = MyPick<User, 'name'>;
// { name: string }

type NoEmail = MyOmit<User, 'email'>;
// { name: string; age: number }

实用图案

Deep Partial(递归映射类型)

type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

可空属性

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

使用映射重命名键

type RenameKeys<T, M extends Record<string, string>> = {
  [K in keyof T as K extends keyof M ? M[K] : K]: T[K];
};

结论

映射类型是 TypeScript 类型转换功能的支柱。从基本修饰符(readonly?)到使用模板文字的高级键重新映射,它们使您可以对类型如何演变进行细粒度控制。与条件类型相结合,它们实现了编译时元编程,使 TypeScript 真正强大。