处理通用功能的记录

时间:2020-09-11 10:38:52

标签: typescript typescript-typings typescript-generics

让我们为处理函数定义以下类型:

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<, >进行两次转换,并带来相关的风险(简单的错字可能会被忽略等等)?

2 个答案:

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

ts playground