'R' 可以用与 'Response' 无关的任意类型实例化

2024-03-26

好吧,我正在尝试在 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;
}

我在最后一行收到以下编译错误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>'.

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

EDIT

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

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

但我还是想知道如何解决这个双重演员。


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

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>;
}

The execute()的方法CommandBus是一个通用函数,声称能够接受command任何亚型的Command调用者想要(到目前为止可能还好),并返回一个值Promise<R>, where R is 的任何子类型Response<C>来电者想要的。这似乎不是任何人都可以合理实现的,并且大概您必须始终断言您返回的响应是R来电者询问。我怀疑这就是你的意图。相反,像这样的事情怎么样:

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

Here, 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()。事实就是这样Handler本身就是一个通用的type。和通用的区别功能和通用的types与谁可以指定类型参数有关。对于函数来说,它是调用者。对于类型来说,它是实现者:

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() method also要求命令的类型C,这太严格了。你想要的caller of canHandle()来选择C,并且返回值为true or false取决于是否C调用者选择的与实现者选择的相匹配。为了在类型系统中表示这一点,我建议canHandle()一个通用的用户定义的类型保护 https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards父接口的方法根本不通用,如下所示:

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, and canHandle()回报true,编译器会理解你的处理程序是Handler<C>你可以调用它。像这样:

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[]'s find()找到一个合适的方法command。编译器无法查看回调handler => handler.canHandle(command)并推断回调的类型(handler: SomeHandler) => handler is SomeHandler<C>,所以我们必须通过这样注释来帮助它。然后编译器会理解的返回值find() is Handler<C> | undefined:

class AppCommandBus implements CommandBus {
    private readonly handlers: SomeHandler[] = [];

    public async execute<C extends Command>(
        command: C
    ): Promise<Response<C>> {
        const resolvedHandler = this.handlers.find((handler): handler is Handler<C> =>
            handler.canHandle(command)
        );
        if (!resolvedHandler) throw new Error();
        return resolvedHandler.handle(command);
    }
}

这与类型系统配合得很好,并且是我能做到的最好的。它可能适用于您的实际用例,也可能不适用于您的实际用例,但希望它能为您提供一些有关如何有效使用泛型的想法。祝你好运!

Playground 代码链接 https://www.typescriptlang.org/play?ts=3.9.2#code/HYQwtgpgzgDiDGEAEBxCwICcCW8BiArsPAC7YD2wUSA3kgFBJNIkCeMyaGO+RpFwJAF4kAHgAqAPgAUADwBcScQEphkpQG4GjZvEpQSSeCGAkAIuXEALbFEVcsuQsTKVhSOYoM5gAc1VC6rIAdCTkAKowHJgAwiBQENLKWgD0KUhYmOSYAIQMzEhpSAAm5NDAAOSGAO7ZANZIAGbZGQBuWKxKOkzGphbWttJ0IIoAROTkMFCjSAC+yYXpmERkkBmYWZj03Ub6hmAEBv02dqjojrwuAu6yakiyqenkdSCsADRI7TiN2CAARgAbTq1TB1ajNTBtDpdAoHI6WE5DJAjJDjF6sGbzR5IEFg+izbagSCwBCcc48cTsaC0HZsDhnbi4SkcCTqESeJQBdTiDTbAp6KiGXrmBG2ezkplU9wc7zYPxc+6hCJRLBxBJJbGZbJ5YDkJBQAjwKwsKl83R7JBwkUDU4OClU0Syvxs+53EJhSLRNWJBZFZ6vD7iJC2fUceDYH4QYo7K3HQbDMYTKaY33pLW5EplKCVQwIRAwQwAAxoKKdvlmhZjh2tiLRr1GyXx23oROgcEQSAAorJwDAAcgaDs5SQsI1SUgYuQwGATMUAEKHGkFAoQWQQeAEEeKUQxDKyEfAYrUSfT2cyPSnw+KGIKgAKWTAtggogASm39M+YpJJLyCgSdvAALxNQACCUQnjOh4LtQ2BgH2ECQKYx5TpB86LoOy7MDAOCtCAI5IJgEAgKUwBAkgVizv2mCnAAylOEAABKUVgADaAC67jsbyOwFDABCArgyJQKwxB7uum6fnuB5HhOKFntIPGYbsl7FNeilMMoij3lOT6vu+VCft+S5KfyFqEVA5AAu0xRMYeVHuCQJzBBRdlYFAwQ-Ie0jSC5xRUZp5HMZCIa2X5WA7mykjqSZgWuZgwS9KF-bSBeqHKNFBSNjFzARh4OTmZZ1lJVgqiOVk1RIBgFWdhs2QahlzCESQBCYIIBVWVGxXxb5yWpbOWVKQSf5mkww6juOdGQF1xmYYlzHbruq7SchKnnnJV4TgqjkhiFQURb+zD-gUY2YGOHZdTuUnoDJEFnldh7UJNjFBTNy49RAigpetqmbXc2mPgkemwB+EU-jsR3MI0fCuIII4GBdi37tdK2oTI72YIoT1dR8fUbTer0FOjznMV9Kmpusmx5JVervZaECOeQxRIHUurVIIrD09FuU+UFCUmMVpNpaoGHZbFYXdSTuPFOT-qsDk0VDYdI3BqY40drdh60HMysnWdyBvsDBmXUtyOyatBPmip14HUwBKzEAA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

'R' 可以用与 'Response' 无关的任意类型实例化 的相关文章

随机推荐