这是我能想象到的最接近的,虽然我仍然不明白为什么我们不从一开始就使用普通对象:
type ObjectToEntries<O extends object> = { [K in keyof O]: [K, O[K]] }[keyof O]
interface ObjectMap<O extends object> {
forEach(callbackfn: <K extends keyof O>(
value: O[K], key: K, map: ObjectMap<O>
) => void, thisArg?: any): void;
get<K extends keyof O>(key: K): O[K];
set<K extends keyof O>(key: K, value: O[K]): this;
readonly size: number;
[Symbol.iterator](): IterableIterator<ObjectToEntries<O>>;
entries(): IterableIterator<ObjectToEntries<O>>;
keys(): IterableIterator<keyof O>;
values(): IterableIterator<O[keyof O]>;
readonly [Symbol.toStringTag]: string;
}
interface ObjectMapConstructor {
new <E extends Array<[K, any]>, K extends keyof any>(
entries: E
): ObjectMap<{ [P in E[0][0]]: Extract<E[number], [P, any]>[1] }>;
new <T>(): ObjectMap<Partial<T>>;
readonly prototype: ObjectMap<any>;
}
const ObjectMap = Map as ObjectMapConstructor;
这个想法是创建一个新的接口, ObjectMap
它特别依赖于对象类型 O
来确定其键/值关系。然后您可以说 Map
构造函数可以充当 ObjectMap
构造函数。我还删除了可以更改实际存在的键的任何方法(并且该 has()
方法 true
也是多余的)。
我可以不厌其烦地解释每个方法和属性定义,但这需要很多类型的处理。简而言之,你想使用 K extends keyof O
和 O[K]
和 K
表示 V
的类型 Map<K, V>
.
构造函数有点烦人,因为类型推断不能按照您希望的方式工作,因此保证类型安全分为两个步骤:
// let the compiler infer the type returned by the constructor
const myMapInferredType = new ObjectMap([
['key1', 'v'],
['key2', 1],
]);
// make sure it's assignable to `ObjectMap<Values>`:
const myMap: ObjectMap<Values> = myMapInferredType;
如果您的 myMapInferredType
不匹配 ObjectMap<Values>
(例如,您缺少键或具有错误的值类型), myMap
则会出现错误。
现在,您可以 myMap
像 ObjectMap<Values>
使用 Map
实例 get()
和 set()
,并且它应该是类型安全的。
请再次注意...对于更复杂的对象来说,这似乎是一项艰巨的工作,其类型更复杂,功能也不比普通对象多。我会严肃地警告任何使用其 Map
键是子类型的 keyof any
(即 string | number | symbol
)的人,强烈 考虑改用普通对象 ,并确保你的用例确实需要 Map
.
游乐场链接到代码