可以使用与'Response <Command>'不相关的任意类型实例化'R'

时间:2020-06-28 14:06:29

标签: typescript compiler-errors

好,所以我试图在TypeScript中实现一个简单的“命令总线”,但是我绊倒了泛型,我想知道是否有人可以帮助我。这是我的代码:

这是命令总线的接口

export default interface CommandBus {
  execute: <C extends Command, R extends Response<C>>(command: C) => Promise<R>;
}

这是实现

export default class AppCommandBus implements CommandBus {
  private readonly handlers: Handler<Command, Response<Command>>[];

  /* ... constructor ... */

  public async execute<C extends Command, R extends Response<C>>(
    command: C
  ): Promise<R> {
    const resolvedHandler = this.handlers.find(handler =>
      handler.canHandle(command)
    );

    /* ... check if undef and throw ... */

    return resolvedHandler.handle(command);
  }
}

这是Handler界面的样子:

export default interface Handler<C extends Command, R extends Response<C>> {
  canHandle: (command: C) => boolean;
  handle: (command: C) => Promise<R>;
}

Command(当前)是一个空接口,Response如下所示:

export default interface Response<C extends Command> {
  command: C;
}

我在commandbus的execute函数的最后一行遇到了跟随编译错误错误,我完全陷入了困境。

type 'Response<Command>' is not assignable to type 'R'. 'R' could be instantiated with an arbitrary type which could be unrelated to 'Response<Command>'.

如果有人能够帮助我了解我在做错什么,我将永远感激不已!

编辑

我意识到我可以通过类型转换解决此问题:

const resolvedHandler = (this.handlers.find(handler =>
  handler.canHandle(command)
) as unknown) as Handler<C, R> | undefined;

但是我仍然想知道如何解决这个双重问题。

1 个答案:

答案 0 :(得分:6)

TypeScript中的泛型函数充当表示其泛型类型参数的每个可能规范的函数,因为指定类型参数的是函数的 caller ,而不是 implementer

type GenericFunction = <T>(x: T) => T;

const cantDoThis: GenericFunction = (x: string) => x.toUpperCase(); // error! 
// doesn't work for every T
cantDoThis({a: "oops"}); // caller chooses {a: string}: runtime error

const mustDoThis: GenericFunction = x => x; // okay, verifiably works for every T
mustDoThis({a: "okay"}); // okay, caller chooses {a: string}

所以,让我们看一下CommandBus

interface CommandBus {
  execute: <C extends Command, R extends Response<C>>(command: C) => Promise<R>;
}

execute()的{​​{1}}方法是一个通用函数,声称能够接受呼叫者想要的CommandBus的任何子类型的command(到目前为止,可能),并返回值Command,其中Promise<R>是呼叫者想要的R的任何子类型。这似乎不是任何人都可以实现的东西,并且大概您必须始终断言所返回的响应是呼叫者要求的Response<C>。我怀疑这是你的意图。取而代之的是:

R

此处,interface CommandBus { execute: <C extends Command>(command: C) => Promise<Response<C>>; } 仅具有一个通用参数execute(),它对应于传入的C的类型。而且返回值只是command,而不是调用者要求的某些子类型。只要您有某种方式保证每个Promise<Response<C>>都有合适的处理程序(例如,如果没有,则C),这似乎更可行。


这将使我们进入您的throw界面:

Handler

即使我们摆脱试图代表处理程序将产生的interface Handler<C extends Command, R extends Response<C>> { canHandle: (command: C) => boolean; handle: (command: C) => Promise<R>; } 特定子类型的专制,也是如此:

Response<C>

我们对interface Handler<C extends Command> { canHandle: (command: C) => boolean; handle: (command: C) => Promise<Response<C>>; } 仍然有疑问。这就是canHandle()本身是通用的 type 的事实。通用功能和通用 types 之间的区别与谁可以指定类型参数有关。对于函数,它是调用者。对于类型,它是实现者:

Handler

您希望type GenericType<T> = (x: T) => T; const cantDoThis: GenericType = (x: string) => x.toUpperCase(); // error! no such type const mustDoThis: GenericType<string> = x => x.toUpperCase(); // okay, T is specified mustDoThis({ a: "oops" }); // error! doesn't accept `{a: string}` mustDoThis("okay"); Handler<C>类型为handle()的命令。但是它的C方法 也要求该命令的类型为canHandle(),这太严格了。您希望C caller 选择canHandle(),并根据{{1}的不同将返回值设为Ctrue。 }}由调用者选择,与实现者选择的匹配。为了在类型系统中表示这一点,我建议使false成为父接口的通用user-defined type guard方法,而该父接口根本不是通用的,就像这样:

C

因此,如果您有canHandle(),则只能打interface SomeHandler { canHandle: <C extends Command>(command: C) => this is Handler<C>; } interface Handler<C extends Command> extends SomeHandler { handle: (command: C) => Promise<Response<C>>; } 。如果向其传递类型为SomeHandler的命令,并且canHandle()返回C,则编译器将理解您的处理程序为canHandle(),您可以调用它。像这样:

true

我们快完成了。唯一奇怪的是,您正在使用Handler<C>的{​​{1}}方法来找到适合function testHandler<C extends Command>(handler: SomeHandler, command: C) { handler.handle(command); // error! no handle method known yet if (handler.canHandle(command)) { handler.handle(command); // okay! } } 的方法。编译器无法查看回调SomeHandler[]并推断出该回调的类型为find(),因此我们必须通过对其进行注释来提供帮助。然后,编译器将了解command的返回值为handler => handler.canHandle(command)

(handler: SomeHandler) => handler is SomeHandler<C>

这在类型系统上很好地工作,并且尽我所能。它可能不适用于您的实际用例,但希望它能为您提供一些有关如何有效使用泛型的想法。祝你好运!

Playground link to code