Typescript - 绑定泛型类型“不可分配”给扩展类型的函数

ᴘᴀɴ*_*ᴛɪs 5 generics types typescript

我有一个基本类型Base,我想创建一个对象来保存接收某些子类型BaseExtendsBaseAExtendsBaseB在本例中)并将其映射到另一种类型的函数C

我试图将“某些子类型Base声明为<T extends Base>,但类型检查失败,并显示以下内容:

类型“(baseA: ExtendsBaseA) => C”不可分配给类型“(base: T) => C”。

参数“base”和“baseA”的类型不兼容。

类型“T”不可分配给类型“ExtendsBaseA”。

类型“Base”不可分配给类型“ExtendsBaseA”。

类型“Base”中缺少属性“b”。

片段

interface Base {
    a: string
}

interface ExtendsBaseA extends Base {
    b: string
}

interface ExtendsBaseB extends Base {
    c: string
}

interface C {}


class Foo {
    private readonly handlers: {
        [key: string]: <T extends Base> (base: T) => C
    }

    constructor() {
        this.handlers = {
            'bar' : this.handler
        }
    }

    handler(baseA: ExtendsBaseA): C {
        return <C>null;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里尝试一下

关于如何解决这个问题有什么想法吗?

编辑:奇怪的是,如果我改变:

[key: string]: <T extends Base> (base: T) => C
Run Code Online (Sandbox Code Playgroud)

到:

[key: string]: (base: Base) => C
Run Code Online (Sandbox Code Playgroud)

它可以在操场上运行,但当我在本地 Typescript 安装上尝试它时就不行了。(都是2.9.1)

jca*_*alz 2

由于您计划handlers成为一个其键对应于可判别联合的判别式的映射,因此您应该能够准确地表示该类型。让我充实一下您必须包含判别式的类型:

interface Base {
  type: string
  a: string
}

interface ExtendsBaseA extends Base {
  type: "ExtendsBaseA"
  b: string
}

interface ExtendsBaseB extends Base {
  type: "ExtendsBaseB"
  c: string
}

interface C { }

type BaseUnion = ExtendsBaseA | ExtendsBaseB;
Run Code Online (Sandbox Code Playgroud)

请注意,您需要显式声明联合,如上所示BaseUnionHandlerMap现在我们可以如下定义类型。

type HandlerMap = { 
  [K in BaseUnion['type']]?: (base: Extract<BaseUnion, { type: K }>) => C 
}
Run Code Online (Sandbox Code Playgroud)

如果你检查一下,它看起来像:

type HandlerMap = {
  ExtendsBaseA?: (base: ExtendsBaseA) => C,
  ExtendsBaseB?: (base: ExtendsBaseB) => C
}
Run Code Online (Sandbox Code Playgroud)

Foo现在你可以像这样定义你的类:

class Foo {
  private readonly handlers: HandlerMap;

  constructor() {
    this.handlers = {
      ExtendsBaseA: this.handler // changed the key
    }
  }

  handler(baseA: ExtendsBaseA): C {
    return <C>null!;
  }

}
Run Code Online (Sandbox Code Playgroud)

就目前而言,这一切都是有效的。不过,您会发现编写一个接受 aHandlerMap和 aBaseUnion并尝试生成 a的类型安全函数是令人沮丧的C

function handle<B extends BaseUnion>(h: HandlerMap, b: B): C | undefined {
  const handler = h[b.type] 
  if (!handler) return;
  return handler(b); // error, no valid call signature
}
Run Code Online (Sandbox Code Playgroud)

TypeScript 编译器的控制流分析不够复杂,无法理解 的参数h[b.type]始终与 的类型完全对应b。相反,它认为h[b.type]接受 的某些组成部分BaseUnion,而 thatb是 的某些组成部分BaseUnion,并且拒绝它们不匹配的可能性。您可以断言它们确实匹配,这可能是您能做的最好的事情:

function handle<B extends BaseUnion>(h: HandlerMap, b: B): C | undefined {
  const handler = h[b.type] as ((b: B) => C) | undefined; 
  if (!handler) return;
  return handler(b); // okay
}
Run Code Online (Sandbox Code Playgroud)

希望这有一些帮助。祝你好运!