阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 映射类型基础

映射类型基础

作者:陈川 阅读数:14316人阅读 分类: TypeScript

映射类型基础

TypeScript 的映射类型是一种基于已有类型创建新类型的强大工具。它允许我们通过遍历现有类型的属性并应用转换规则来生成新类型,这种机制在处理复杂类型时特别有用。

基本语法

映射类型的核心语法使用 in 关键字遍历联合类型:

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

这个最简单的映射类型实际上创建了一个与原始类型 T 完全相同的新类型。keyof T 获取类型 T 的所有属性名组成的联合类型,P in 则遍历这个联合类型中的每个属性。

常见内置映射类型

TypeScript 内置了几个常用的映射类型:

// 使所有属性变为可选
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// 使所有属性变为必需
type Required<T> = {
  [P in keyof T]-?: T[P];
};

// 使所有属性变为只读
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

属性修饰符操作

映射类型可以操作属性的修饰符(?readonly):

// 移除可选修饰符
type Concrete<T> = {
  [P in keyof T]-?: T[P];
};

// 添加readonly修饰符
type Locked<T> = {
  +readonly [P in keyof T]: T[P];
};

// 移除readonly修饰符
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

键名重映射

TypeScript 4.1 引入了键名重映射,允许在映射过程中修改属性名:

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

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

type PersonGetters = Getters<Person>;
// 等价于:
// {
//   getName: () => string;
//   getAge: () => number;
// }

条件类型与映射类型结合

映射类型可以与条件类型结合使用,实现更复杂的类型转换:

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

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

type NullableUser = NullableProperties<User>;
// 等价于:
// {
//   id: number | null;
//   name: string | null;
//   email: string | null;
// }

过滤属性

通过条件类型可以过滤掉某些属性:

type OnlyFunctions<T> = {
  [P in keyof T as T[P] extends Function ? P : never]: T[P];
};

interface Mixed {
  id: number;
  name: string;
  update: (data: Partial<Mixed>) => void;
  delete: () => Promise<void>;
}

type Functions = OnlyFunctions<Mixed>;
// 等价于:
// {
//   update: (data: Partial<Mixed>) => void;
//   delete: () => Promise<void>;
// }

实际应用示例

表单类型生成

interface User {
  username: string;
  password: string;
  age: number;
}

type FormField<T> = {
  value: T;
  isValid: boolean;
  errorMessage?: string;
};

type UserForm = {
  [P in keyof User]: FormField<User[P]>;
};

// 等价于:
// {
//   username: FormField<string>;
//   password: FormField<string>;
//   age: FormField<number>;
// }

API响应包装

type ApiResponse<T> = {
  data: T;
  status: number;
  timestamp: Date;
};

type Entity = {
  id: string;
  createdAt: Date;
};

type EntityResponse<T extends Entity> = ApiResponse<{
  [P in keyof T]: T[P];
}>;

interface Product extends Entity {
  name: string;
  price: number;
}

type ProductResponse = EntityResponse<Product>;
// 等价于:
// ApiResponse<{
//   id: string;
//   createdAt: Date;
//   name: string;
//   price: number;
// }>

高级模式

递归映射

映射类型可以递归应用于嵌套对象:

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

interface Company {
  name: string;
  departments: {
    name: string;
    employees: number;
  }[];
}

type ReadonlyCompany = DeepReadonly<Company>;
// 等价于:
// {
//   readonly name: string;
//   readonly departments: readonly {
//     readonly name: string;
//     readonly employees: number;
//   }[];
// }

联合类型映射

映射类型也可以用于联合类型:

type EventMap = {
  click: MouseEvent;
  focus: FocusEvent;
  keydown: KeyboardEvent;
};

type Handlers<T> = {
  [P in keyof T as `on${Capitalize<string & P>}`]: (event: T[P]) => void;
};

type EventHandlers = Handlers<EventMap>;
// 等价于:
// {
//   onClick: (event: MouseEvent) => void;
//   onFocus: (event: FocusEvent) => void;
//   onKeydown: (event: KeyboardEvent) => void;
// }

性能考虑

虽然映射类型非常强大,但在处理大型类型或深度嵌套类型时需要注意性能问题。TypeScript 编译器需要展开这些类型,复杂的映射类型可能会导致编译速度变慢或内存使用增加。

// 避免过度嵌套的映射类型
type OverlyComplex<T> = {
  [P in keyof T]: {
    [Q in keyof T[P]]: {
      [R in keyof T[P][Q]]: T[P][Q][R];
    };
  };
};

与模板字面量类型结合

TypeScript 4.1 引入的模板字面量类型可以与映射类型完美配合:

type WithChanges<T> = {
  [P in keyof T as `${string & P}Changed`]: (newValue: T[P]) => void;
};

interface Settings {
  theme: string;
  fontSize: number;
}

type SettingsChanges = WithChanges<Settings>;
// 等价于:
// {
//   themeChanged: (newValue: string) => void;
//   fontSizeChanged: (newValue: number) => void;
// }

实用工具类型实现

许多实用工具类型都是基于映射类型实现的:

// 选择特定属性
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// 排除特定属性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// 记录类型
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

类型谓词与映射类型

映射类型可以与类型谓词结合,创建更精确的类型守卫:

type TypeGuardMap<T> = {
  [P in keyof T]: (value: unknown) => value is T[P];
};

interface Config {
  port: number;
  hostname: string;
  ssl: boolean;
}

const configGuards: TypeGuardMap<Config> = {
  port: (value): value is number => typeof value === 'number',
  hostname: (value): value is string => typeof value === 'string',
  ssl: (value): value is boolean => typeof value === 'boolean',
};

映射类型与泛型约束

映射类型可以配合泛型约束创建更安全的API:

type NonMethodKeys<T> = {
  [P in keyof T]: T[P] extends Function ? never : P;
}[keyof T];

type DataProperties<T> = Pick<T, NonMethodKeys<T>>;

class User {
  id: string;
  name: string;
  save() {
    // 保存逻辑
  }
}

type UserData = DataProperties<User>;
// 等价于:
// {
//   id: string;
//   name: string;
// }

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌