
在typescript的类型系统中,我们经常会遇到需要基于现有类型派生出新类型场景。一个常见的需求是,我们希望创建一个新类型,它拥有与某个现有对象类型完全相同的属性键,但所有这些属性的类型都被统一设置为 any。这种转换在处理动态数据、api响应或需要临时忽略类型检查的场景中非常有用。
考虑以下一个典型的类定义:
class Foo {
foo: string;
bar: number;
baz: Date;
}我们的目标是生成一个像 TransmutedFoo 这样的接口,它拥有 Foo 的所有属性键,但所有值类型均为 any:
interface TransmutedFoo {
foo: any;
bar: any;
baz: any;
}接下来,我们将介绍两种实现这种泛型转换的方法。
方法一:使用自定义映射类型 (Mapped Types)
映射类型是TypeScript中一个非常强大的特性,它允许我们基于旧类型转换出新类型,通常通过遍历旧类型的属性键来完成。它的基本语法是 [Key in K]: Type,其中 K 是一个联合类型,表示要遍历的键集合。
要实现 Transmuted
type Transmuted= { [Key in keyof T]: any; };
让我们分解一下这个定义:
- keyof T: 这是一个索引访问类型操作符,它会提取类型 T 的所有公共属性键,并返回一个字符串字面量联合类型。例如,对于 Foo 类,keyof Foo 将是 'foo' | 'bar' | 'baz'。
- [Key in keyof T]: 这就是映射类型的核心。它会遍历 keyof T 返回的所有键,并将每个键赋值给 Key 变量。
- : any: 对于每个 Key,我们将其对应的属性类型设置为 any。
将这个泛型应用到我们的 Foo 类上,可以得到预期的结果:
class Foo {
foo: string;
bar: number;
baz: Date;
constructor() {
this.foo = "";
this.bar = 0;
this.baz = new Date();
}
}
type Transmuted = {
[Key in keyof T]: any;
};
type TransmutedFoo = Transmuted;
// TransmutedFoo 的类型将是:{ foo: any; bar: any; baz: any; } 这种方法非常灵活,不仅可以将属性类型统一设置为 any,还可以根据需要设置为其他类型,甚至进行更复杂的类型转换。
方法二:使用内置的 Record 工具类型
对于将所有属性类型统一设置为相同类型的特定场景,TypeScript提供了一个内置的工具类型 Record
Record 类型的定义实际上就是基于映射类型实现的:
type Record= { [P in K]: T; };
利用 Record,我们可以更简洁地实现 Transmuted
type Transmuted= Record ;
这里:
- keyof T: 再次用于获取类型 T 的所有属性键。
- any: 指定所有这些属性键对应的值类型都为 any。
同样,将其应用到 Foo 类上:
class Foo {
foo: string;
bar: number;
baz: Date;
constructor() {
this.foo = "";
this.bar = 0;
this.baz = new Date();
}
}
type Transmuted = Record;
type TransmutedFoo = Transmuted;
// TransmutedFoo 的类型同样是:{ foo: any; bar: any; baz: any; } 总结与选择
两种方法都能有效地实现将一个对象类型的属性键保留,但将其所有属性类型统一转换为 any 的目标。
-
自定义映射类型 (type Transmuted
= { [Key in keyof T]: any; };): 这种方法更基础、更通用。当你需要进行更复杂的类型转换,例如根据原始属性类型动态决定新属性类型,或者过滤掉某些属性时,自定义映射类型是首选。它提供了最大的灵活性。 -
Record 工具类型 (type Transmuted
= Record ;): 当你的需求仅仅是将一组已知的键(或从现有类型中提取的键)映射到同一种特定的值类型时,Record 类型提供了一种更简洁、更具可读性的方式。它封装了映射类型的常见用法,使得代码更易于理解和维护。
在本文给出的特定场景中,由于所有属性都统一转换为 any,使用 Record










