索引签名与映射类型
TypeScript 的索引签名和映射类型是处理动态属性和批量转换类型的核心工具。索引签名允许对象拥有灵活的结构,而映射类型则能基于现有类型生成新类型,两者结合可以极大提升代码的灵活性和可维护性。
索引签名的基本语法
索引签名允许我们定义对象中未知属性的类型。语法结构为 [key: KeyType]: ValueType
,其中 KeyType
通常是 string
、number
或 symbol
。例如:
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;
};
性能考量与最佳实践
过度使用索引签名会削弱类型安全性。推荐的做法是:
- 优先使用精确类型
- 为索引签名添加尽可能具体的类型约束
- 对大型对象考虑使用
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
上一篇:类型别名与接口比较
下一篇:条件类型与分布式条件类型