typescript - “R”可以用与“响应”无关的任意类型实例化 '
问题描述
好的,所以我正在尝试在 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>'.
如果有人能够帮助我理解我做错了什么,我将永远感激不尽!
编辑
我意识到我可以通过类型转换来解决这个问题:
const resolvedHandler = (this.handlers.find(handler =>
handler.canHandle(command)
) as unknown) as Handler<C, R> | undefined;
但我仍然想知道如何解决这个双重演员。
解决方案
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}
那么,让我们看看CommandBus
:
interface CommandBus {
execute: <C extends Command, R extends Response<C>>(command: C) => Promise<R>;
}
的execute()
方法CommandBus
是一个通用函数,它声称能够接受调用者想要command
的任何子类型的a 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
如果您没有,则通过 ing ) ,这更有可能实现。
这将我们带到您的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 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()
type 的命令C
,这很好。但是它的canHandle()
方法也要求命令是 type C
,这太严格了。您希望调用者选择canHandle()
,C
并且返回值是 true
或false
取决于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()
。如果你向它传递一个 type 的命令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!
}
}
我们快完成了。唯一需要注意的是,您正在使用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);
}
}
这适用于类型系统,并且尽我所能。它可能适用于您的实际用例,也可能不适用,但希望它能给您一些关于如何有效使用泛型的想法。祝你好运!
推荐阅读
- python - 程序一直在无限函数中循环?
- asp.net-mvc - Json 格式的 web api 的结果应该只返回特定的数据
- postgresql - 为什么postgres容器在Gitlab CI中忽略/docker-entrypoint-initdb.d/*
- python-2.7 - Python 2 - 除法乘以 10 每次返回 0
- excel - 如何在不将值转换为公式的情况下更改 Power Query 中的参数?
- arrays - 如何使用Java中文本文件中的元素对平均标记进行排序
- sql - 带有计数的数据透视表 - Oracle SQL
- java - ImageView 用相同的按钮交换图像
- mysql - 从多个表中选择多个 JOIN
- javascript - 选择选项元素 JavaScript 控制台