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