在TypeScript中实现带有元数据的ES6 Map扩展

时间:2018-09-30 13:59:07

标签: typescript dictionary generics

我需要一个映射,其中键不仅与值关联,而且还与某些元数据关联,例如“ weight”或“ timeToLive”。

首先,一个Map界面示例,其中包含不同权重的项目:

export interface Weighted {
    weight: number
}

...以及用于将所有元数据接口与实际值合并以保存在Map中的类型别名:

type AugmValue<V, M> = M & {
    value: V;
}

我拿了内置Map并将其扩展。从通用类型开始:

  • M用于元数据接口,例如Weighted & Ttl
  • K是一种键
  • V是一种实际值

...和实现:

export class MdaMap<M, K, V> extends Map<K, AugmValue<V, M>> {
    constructor(records: [K, V, M][] = []) {
        const result: [K, AugmValue<V, M>][] = [];
        for (const [key, val, meta] of records) {
            result.push([key, Object.assign({value: val}, meta)]);
        }
        super(result);
    }

    get(key: K): V | undefined {
        const t = super.get(key);
        return typeof t === 'undefined' ? undefined : t.value;
    }
}

但是,get的下划线标有以下内容的冗长消息:

Type 'V' is not assignable to type 'AugmValue<V, M>'.
      Type 'V' is not assignable to type 'M'.

如何正确实施?这是简化的情况,最终我想拥有get()这样的方法:

get(key: K, meta: keyof AugmValue<V, M> = 'value'): AugmValue<V, M> [keyof M | "value"] | undefined {
    const t = super.get(key);
    return typeof t === 'undefined' ? undefined : t[meta];
}

1 个答案:

答案 0 :(得分:1)

如果扩展Map<K, AugmValue<V, M>>,则扩展名需要实现that interface,这意味着get(key: K)需要返回AugmValue<V, M>> | undefined,而不是V | undefined

您可以使用的一种可能性是overload get()方法,以便它仍然实现所需的签名,但还接受附加的参数以提供额外的行为:

export class MdaMap<M, K, V> extends Map<K, AugmValue<V, M>> {

  get(key: K): AugmValue<V, M> | undefined;
  get<AK extends keyof AugmValue<V, M>>(
    key: K, 
    augmentedValueKey: AK
  ): AugmValue<V, M>[AK] | undefined;
  get<AK extends keyof AugmValue<V, M>>(
    key: K, 
    augmentedValueKey?: AK
  ): AugmValue<V, M> | AugmValue<V, M>[AK] | undefined {
    const t = super.get(key);
    return typeof augmentedValueKey === 'undefined' ? t : 
      typeof t === 'undefined' ? undefined : t[augmentedValueKey];
  }

}

const mdaMap = new MdaMap([["a", true, { weight: 4 }], ["b", false, { weight: 17 }]]);
const augmented = mdaMap.get("a"); // AugmValue<boolean, Weighted> | undefined
const unaugmented = mdaMap.get("a", "value"); // boolean | undefined
const weight = mdaMap.get("a", "weight"); // number | undefined

这具有(可能是次要的)缺点,get(key)不会返回您希望的内容,而您需要调用get(key, "value")……但是它具有简洁且易于使用的巨大优势。正确键入。


如果确实需要制作某种与基类不兼容的派生类(如果get(key)需要返回V | undefined),则不能使用{{1} },因为您没有产生有效的子类型。在这种情况下,通常的建议是您的新类实例应具有基类的实例,而不是试图成为基类的实例。这称为composition over inheritance。从概念上讲这很清楚,但是在实践中却很烦人,因为它最终会将大多数方法转发到保留的实例:

extends

那真是太痛苦了,我不知道您是否应该使用一个class OtherMap<M, K, V> { private innerMap: Map<K, AugmValue<V, M>> = new Map(); constructor(records: [K, V, M][] = []) { this.innerMap = new Map(records.map( ([k, v, m]) => [k, Object.assign({ value: v }, m)] as [K, AugmValue<V, M>] )); } clear(): void { return this.innerMap.clear(); } delete(key: K): boolean { return this.innerMap.delete(key); } forEach(callbackfn: (value: M & { value: V; }, key: K, map: OtherMap<M, K, V>) => void, thisArg?: any): void { return this.innerMap.forEach((v, k) => callbackfn(v, k, this), thisArg); } get(key: K): V | undefined { return (this.innerMap.get(key) || { value: undefined }).value; } has(key: K): boolean { return this.innerMap.has(key); } set(key: K, value: M & { value: V; }): this { this.innerMap.set(key, value); return this; } get size(): number { return this.innerMap.size; } [Symbol .iterator](): IterableIterator<[K, M & { value: V; }]> { return this.innerMap[Symbol.iterator](); } entries(): IterableIterator<[K, M & { value: V; }]> { return this.innerMap.entries(); } keys(): IterableIterator<K> { return this.innerMap.keys(); } values(): IterableIterator<M & { value: V; }> { return this.innerMap.values(); } [Symbol.toStringTag]: "Map"; } 而不是尝试创建一个新的类?


无论如何,希望这些想法之一能为您提供帮助。祝你好运!