Typescript:如何根据对象键/值类型在 ES6 映射中创建条目

Nik*_*tad 9 typescript ecmascript-6 typescript-generics union-types es6-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],
]);
Run Code Online (Sandbox Code Playgroud)

编辑:更多部分描述我的用例的代码

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]
]);
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 9

这是我能想象到的最接近的结果,尽管我仍然不明白为什么我们不直接使用普通对象:

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;
Run Code Online (Sandbox Code Playgroud)

这个想法是创建一个新的接口,ObjectMap它专门依赖于对象类型O来确定其键/值关系。然后你可以说Map构造函数可以充当ObjectMap构造函数。我还删除了任何可以更改实际存在的键的方法(并且该has()方法也是多余的true)。

我可以不厌其烦地解释每个方法和属性定义,但这需要大量的类型处理。简而言之,您想要使用K extends keyof O和来表示通常由和中O[K]表示的类型。KVMap<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;
Run Code Online (Sandbox Code Playgroud)

如果您的myMapInferredType不匹配ObjectMap<Values>(例如,您缺少键或具有错误的值类型),则会myMap出现错误。

现在您可以使用myMapas an ObjectMap<Values>,类似于使用Map实例的方式,使用get()and set(),并且它应该是类型安全的。

请再次注意...对于一个更复杂的对象来说,这似乎需要做很多工作,它的类型更复杂,而且没有比普通对象更多的功能。我会严重警告任何使用 其Map键是 的子类型keyof any(即string | number | symbol)的人强烈考虑使用普通对象,并确保您的用例确实需要Map.

Playground 代码链接

  • @jcalz 从那时起,TypeScript 有什么变化吗?我也非常希望能够使用 Map,因为我需要能够对属性进行排序。 (2认同)