令人困惑的地图类型

时间:2020-10-29 14:03:55

标签: typescript

我正在尝试构建一个类似Map的类,该类将一种类型的键映射到另一种类型的键。一个简单的解决方案是仅创建一个Map<keyof A, keyof B>并将其调用完成。但这不会检查AB中每个键的成员类型是否匹配。

我开始了以下课程,我希望做一些类型断言,证明T不是never

class KeyMap<A, B> {
  private mapping = new Map<keyof A, keyof B>();

  add<KA extends keyof A, KB extends keyof B, T = A[KA] & B[KB]>(
    a: KA,
    b: KB
  ): void {
    this.mapping.set(a, b);
  }
}

我不知道这是否可能,但是我可以想象这将是一个有用的地图。

要退后一步,我正在尝试构建一个可重用的实用程序,该实用程序将以类型安全的方式在AB之间进行转换,以验证所有成员类型是否匹配。如果有更好的方法可以做到这一点,那么我愿意接受其他建议。

1 个答案:

答案 0 :(得分:1)

抛开最后一个“是否有更好的方法来解决这个问题”,这就是我要如何使add()要求将b参数限制为仅来自{{1}的键},其中B参数中A中的对应属性有一些重叠(例如,a不是A[KA] & B[KB]):

never

type KeysWithOverlap<T, V> = { [K in keyof T]-?: (T[K] & V) extends never ? never : K }[keyof T]; class KeyMap<A, B> { private mapping = new Map<keyof A, keyof B>(); add<KA extends keyof A, KB extends KeysWithOverlap<B, A[KA]>>( a: KA, b: KB ): void { this.mapping.set(a, b); } } 接受类型KeysWithOverlap<T, V>和值类型T,并返回其属性与V重叠的T的所有键。然后,我们要做的就是将V约束为KB,而不是约束keyof B。让我们看看它是如何工作的:

KeysWithOverlap<B, A[KA]>

请注意,由于interface Foo { a: string; b: 3; c: boolean; } interface Bar { d: "str"; e: number; f: boolean; } const km = new KeyMap<Foo, Bar>(); km.add("a", "e"); // error! e is not assignable to d km.add("a", "d"); // okay km.add("b", "e"); // okay km.add("c", "f"); // okay add("a", "e")没有重叠,因此在尝试呼叫string时会出现错误。其他调用起作用,因为类型兼容。


那么有更好的方法吗?

也许; “没有重叠”可能不够严格;您是否在寻找可以从number中读取A[KA]的类型?还是您一个的人?给定上面的B[KB]Foo类型,如果将Bar映射到Foo或从Bar映射Bar["d"]会发生什么问题,因为"str"仅接受特定的字符串"a",如果您尝试仅将Foo的{​​{1}}属性复制到它,则需要小心。

如果要尝试创建通用的属性映射函数,则可能会在类型系统中表示特定键映射的地方使用它,例如,declare function convert<T, M extends {[K in keyof T]: PropertyKey}>(obj: T, keyMap: M): ConvertKeys<T, M>;用于{{1}的适当定义}。我也许可以写下来,但这会付出更多的努力,并且从我认为这个问题的重点出发。


Playground link to code

相关问题