Typescript:如何根据对象 key/value 类型在 ES6 Map 中创建条目
Typescript: How can I make entries in an ES6 Map based on an object key/value type
我想使用 Map 而不是对象映射来声明一些键和值。但是 Typescript 似乎不支持 ES6 Map 的索引类型,是否正确,是否有任何解决方法?
此外,我还想使值类型安全,以便映射中的每个条目都具有与键对应的值的正确类型。
这是一些描述我想要实现的目标的伪代码:
type Keys = 'key1' | 'key2';
type Values = {
'key1': string;
'key2': number;
}
/** Should display missing entry error */
const myMap = new Map<K in Keys, Values[K]>([
['key1', 'error missing key'],
]);
/** Should display wrong value type error for 'key2' */
const myMap = new Map<K in Keys, Values[K]>([
['key1', 'okay'],
['key2', 'error: this value should be number'],
]);
/** Should pass */
const myMap = new Map<K in Keys, Values[K]>([
['key1', 'all good'],
['key2', 42],
]);
编辑:部分描述我的用例的更多代码
enum Types = {
ADD = 'ADD',
REMOVE = 'REMOVE',
};
/** I would like type-safety and autocompletion for the payload parameter */
const handleAdd = (state, payload) => ({...state, payload});
/** I would like to ensure that all types declared in Types are implemented */
export const reducers = new Map([
[Types.ADD, handleAdd],
[Types.REMOVE, handleRemove]
]);
这是我能想到的最接近的结果,尽管我仍然不明白为什么我们不直接使用普通对象开始:
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
来确定它的key/value关系。然后你可以说 Map
构造函数可以充当 ObjectMap
构造函数。我还删除了任何可以更改实际存在的键的方法(并且 has()
方法也是冗余的 true
)。
我可以不费吹灰之力地解释每个方法和 属性 定义,但这需要大量的类型转换。总之你想用K extends keyof O
和O[K]
来表示Map<K, V>
.
中通常由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
)以强烈 consider using a plain object 代替,并确保您的用例确实需要Map
.
我想使用 Map 而不是对象映射来声明一些键和值。但是 Typescript 似乎不支持 ES6 Map 的索引类型,是否正确,是否有任何解决方法?
此外,我还想使值类型安全,以便映射中的每个条目都具有与键对应的值的正确类型。
这是一些描述我想要实现的目标的伪代码:
type Keys = 'key1' | 'key2';
type Values = {
'key1': string;
'key2': number;
}
/** Should display missing entry error */
const myMap = new Map<K in Keys, Values[K]>([
['key1', 'error missing key'],
]);
/** Should display wrong value type error for 'key2' */
const myMap = new Map<K in Keys, Values[K]>([
['key1', 'okay'],
['key2', 'error: this value should be number'],
]);
/** Should pass */
const myMap = new Map<K in Keys, Values[K]>([
['key1', 'all good'],
['key2', 42],
]);
编辑:部分描述我的用例的更多代码
enum Types = {
ADD = 'ADD',
REMOVE = 'REMOVE',
};
/** I would like type-safety and autocompletion for the payload parameter */
const handleAdd = (state, payload) => ({...state, payload});
/** I would like to ensure that all types declared in Types are implemented */
export const reducers = new Map([
[Types.ADD, handleAdd],
[Types.REMOVE, handleRemove]
]);
这是我能想到的最接近的结果,尽管我仍然不明白为什么我们不直接使用普通对象开始:
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
来确定它的key/value关系。然后你可以说 Map
构造函数可以充当 ObjectMap
构造函数。我还删除了任何可以更改实际存在的键的方法(并且 has()
方法也是冗余的 true
)。
我可以不费吹灰之力地解释每个方法和 属性 定义,但这需要大量的类型转换。总之你想用K extends keyof O
和O[K]
来表示Map<K, V>
.
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
)以强烈 consider using a plain object 代替,并确保您的用例确实需要Map
.