阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 索引签名与映射类型

索引签名与映射类型

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

TypeScript 的索引签名和映射类型是处理动态属性和批量转换类型的核心工具。索引签名允许对象拥有灵活的结构,而映射类型则能基于现有类型生成新类型,两者结合可以极大提升代码的灵活性和可维护性。

索引签名的基本语法

索引签名允许我们定义对象中未知属性的类型。语法结构为 [key: KeyType]: ValueType,其中 KeyType 通常是 stringnumbersymbol。例如:

interface DynamicObject {
  [key: string]: number;
}

const scores: DynamicObject = {
  math: 90,
  physics: 85,
  // 允许动态添加属性
  chemistry: 88
};

当索引签名与已知属性共存时,已知属性的类型必须是索引签名类型的子集:

interface MixedObject {
  name: string;  // 错误:string 不兼容于 number
  [key: string]: number;
}

// 正确写法
interface ValidMixedObject {
  name: never;   // 使用 never 禁止该属性
  [key: string]: number;
}

索引签名的实际应用

索引签名特别适合处理动态数据结构。比如处理 API 返回的字典数据:

interface ApiResponse {
  status: number;
  data: {
    [resourceType: string]: any;
  };
}

const response: ApiResponse = {
  status: 200,
  data: {
    users: [{ id: 1, name: "Alice" }],
    products: [{ id: 101, price: 99 }]
  }
};

数字索引签名常用于类数组对象:

interface StringArray {
  [index: number]: string;
  length: number;
}

const arr: StringArray = ["a", "b"];
console.log(arr[0]);  // "a"

映射类型的核心概念

映射类型通过 in 关键字遍历联合类型来生成新类型。基本语法为:

type Keys = "name" | "age";
type Person = {
  [K in Keys]: string;
};

// 等价于
type Person = {
  name: string;
  age: string;
};

TypeScript 内置了常用映射类型如 Partial<T>Readonly<T>

interface Todo {
  title: string;
  completed: boolean;
}

type PartialTodo = Partial<Todo>;
// { title?: string; completed?: boolean }

type ReadonlyTodo = Readonly<Todo>;
// { readonly title: string; readonly completed: boolean }

高级映射类型技巧

可以结合条件类型实现更复杂的转换:

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

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

type NullableUser = Nullable<User>;
// { name: string | null; age: number | null }

通过 as 子句重映射键名:

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 MakeOptional<T> = {
  [K in keyof T]?: T[K];
} & {
  [key: string]: unknown;
};

interface Config {
  apiUrl: string;
  timeout: number;
}

type FlexibleConfig = MakeOptional<Config>;
// 既有可选已知属性,也允许未知属性

处理枚举类型时特别有用:

enum LogLevel {
  ERROR,
  WARN,
  INFO
}

type LogConfig = {
  [Level in LogLevel]: string;
};

// 等价于
type LogConfig = {
  0: string;
  1: string;
  2: string;
};

性能考量与最佳实践

过度使用索引签名会削弱类型安全性。推荐的做法是:

  1. 优先使用精确类型
  2. 为索引签名添加尽可能具体的类型约束
  3. 对大型对象考虑使用 Record 工具类型
// 优于索引签名
type PageInfo = Record<"home" | "about" | "contact", string>;

// 明确限制值类型
interface SafeDictionary {
  [key: string]: "read" | "write" | "execute";
}

类型体操实战示例

实现一个深度只读类型:

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

interface Nested {
  a: number;
  b: {
    c: boolean;
    d: {
      e: string;
    };
  };
}

type ReadonlyNested = DeepReadonly<Nested>;

处理联合类型键值:

type Flip<T extends Record<string, any>> = {
  [K in keyof T as `${T[K]}`]: K;
};

type Original = { a: "1"; b: "2" };
type Flipped = Flip<Original>;  // { "1": "a"; "2": "b" }

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

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

前端川

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