让我们为处理函数定义以下类型:
type Handler<T, U> = (x: T) => U;
然后说有一个带处理程序字典的库(然后将其用于以某种方式将数据路由到特定的处理程序,与问题无关)
registerHandlers(handlers: Record<string, Handler<any, any>>) {
...
}
这很好,我可以将文字对象传递给registerHandlers
函数:
registerHandlers({
foo: (x: number) => 'foo',
bar: (x: string) => 42
});
但是,为了使接口更清晰并便于对处理程序进行存根和测试,我想定义一个描述我的处理程序的接口:
interface Handlers {
foo: Handler<number, string>;
bar: Handler<string, number>;
}
通过这种方式,可以轻松地定义或覆盖具有所有推断类型的处理程序集(显然,我的示例已简化了,我的实际代码具有更复杂的类型,这就是为什么我要为整个集合定义一个清晰的类型的原因处理程序):
const handlers: Handlers = {
foo: x => 'foo',
bar: x => 42
}
但是,当我尝试通过调用Record<string, Handler<any, any>>
来将接口用作registerHandlers(handlers)
时,即使使用as Record<...>
也不起作用(除非我通过{ {1}}似乎总是万不得已):
unknown
我的Index signature is missing in type Handlers
接口显然是一个具有一组Handlers
函数的对象,因此在实践中,它应该与Handler
相同,但不是。我想我明白为什么不能保证任意接口都与Record
匹配,但是有一种很好的方法来定义类型,以便它是Record
但仍然声明为每个单独属性的类型是正确键入的Record<string, Handler<any, any>>
吗?还是我坚持通过Handler<, >
进行两次转换,并带来相关的风险(简单的错字可能会被忽略等等)?
答案 0 :(得分:1)
您可以通过重新定义registerHandler
期望的参数来解决此问题:
type Handler<T, U> = (x: T) => U;
interface Handlers {
foo: Handler<number, string>;
bar: Handler<string, number>;
}
const handlers: Handlers = {
foo: x => 'foo',
bar: x => 42
}
declare class Foo {
registerHandlers<T extends string>(handlers: Record<T, Handler<any, any>>): void;
}
new Foo().registerHandlers(handlers);
答案 1 :(得分:0)
这是界面行为的known issue。
一个简单的解决方法是在这里使用类型别名而不是接口,如果它不影响您将来的代码,因为它们(通常)是可互换的。
type Handlers = {
foo: Handler<number, string>;
bar: Handler<string, number>;
}