Ben*_*ght 14 compiler-errors typescript
好的,所以我正在尝试在 TypeScript 中实现一个简单的“命令总线”,但是我被泛型绊倒了,我想知道是否有人可以帮助我。这是我的代码:
这是命令总线的接口
export default interface CommandBus {
execute: <C extends Command, R extends Response<C>>(command: C) => Promise<R>;
}
Run Code Online (Sandbox Code Playgroud)
这是实现
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是Handler界面的样子:
export default interface Handler<C extends Command, R extends Response<C>> {
canHandle: (command: C) => boolean;
handle: (command: C) => Promise<R>;
}
Run Code Online (Sandbox Code Playgroud)
Command是(当前)一个空接口,Response看起来像这样:
export default interface Response<C extends Command> {
command: C;
}
Run Code Online (Sandbox Code Playgroud)
我收到了针对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;
Run Code Online (Sandbox Code Playgroud)
但我仍然想知道如何解决这个双重演员。
jca*_*alz 24
TypeScript 中的泛型函数充当表示其泛型类型参数的所有可能规范的函数,因为指定类型参数的是函数的调用者,而不是实现者:
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}
Run Code Online (Sandbox Code Playgroud)
所以,让我们看看CommandBus:
interface CommandBus {
execute: <C extends Command, R extends Response<C>>(command: C) => Promise<R>;
}
Run Code Online (Sandbox Code Playgroud)
的execute()方法CommandBus是一个泛型函数,声称能够接受调用者想要command的任何子类型的a Command(到目前为止可能还好),并返回 的值Promise<R>,其中R是调用者想要的任何子类型Response<C>。这似乎不是任何人都可以合理实现的东西,并且大概您将始终必须断言您返回的响应是R调用者要求的响应。我怀疑这是你的意图。相反,这样的事情怎么样:
interface CommandBus {
execute: <C extends Command>(command: C) => Promise<Response<C>>;
}
Run Code Online (Sandbox Code Playgroud)
这里,execute()只有一个泛型参数,C对应传入的类型command。并且返回值只是Promise<Response<C>>,而不是调用者要求的某些子类型。这更有可能实现,只要你有某种方式保证你有一个合适的处理程序C(比如,throw如果你不这样做,则通过ing。)
这将我们带到您的Handler界面:
interface Handler<C extends Command, R extends Response<C>> {
canHandle: (command: C) => boolean;
handle: (command: C) => Promise<R>;
}
Run Code Online (Sandbox Code Playgroud)
即使我们摆脱了试图表示Response<C>处理程序的特定子类型的暴政,也会产生如下结果:
interface Handler<C extends Command> {
canHandle: (command: C) => boolean;
handle: (command: C) => Promise<Response<C>>;
}
Run Code Online (Sandbox Code Playgroud)
我们仍然有问题canHandle()。这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");
Run Code Online (Sandbox Code Playgroud)
你Handler<C>只想要handle()一个 type 的命令C,这很好。但是它的canHandle()方法也要求命令是 type C,这太严格了。您希望来电者的canHandle()选择C,并有返回值是 true还是false取决于whetehr的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>>;
}
Run Code Online (Sandbox Code Playgroud)
所以,如果你有一个SomeHandler,你所能做的就是调用canHandle()。如果您向它传递一个类型为 的命令C并canHandle()返回true,则编译器将理解您的处理程序是 aHandler<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!
}
}
Run Code Online (Sandbox Code Playgroud)
我们快完成了。唯一令人费解的是,您正在使用SomeHandler[]的find()方法来定位一个适合command. 编译器无法窥视回调handler => handler.canHandle(command)并推断回调是 type (handler: SomeHandler) => handler is SomeHandler<C>,因此我们必须通过对其进行注释来帮助它。那么编译器就会明白的返回值find()是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);
}
}
Run Code Online (Sandbox Code Playgroud)
这与类型系统配合得很好,并且尽我所能。它可能适用于您的实际用例,也可能不起作用,但希望它为您提供有关如何有效使用泛型的一些想法。祝你好运!
| 归档时间: |
|
| 查看次数: |
13604 次 |
| 最近记录: |