【问题标题】:Handling records of generic functions处理泛型函数的记录
【发布时间】:2020-09-11 19:09:28
【问题描述】:

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

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
}

但是,当我尝试通过调用registerHandlers(handlers) 将我的界面用作Record&lt;string, Handler&lt;any, any&gt;&gt; 时,它不起作用,即使使用as Record&lt;...&gt; 也不行(除非我通过unknown 进行双重转换,这似乎总是最后的手段):

Index signature is missing in type Handlers

我的Handlers 接口显然是一个具有一组Handler 函数的对象,因此在实践中,它应该匹配相同的Record,但事实并非如此。我想我理解为什么不能保证任意接口与Record 匹配,但是有没有一种很好的方法来定义一个类型,以便它是Record&lt;string, Handler&lt;any, any&gt;&gt; 但仍然声明为每个单独的属性都是正确类型的类型Handler&lt;, &gt;?还是我坚持通过unknown 进行双重转换,存在相关风险(一个简单的错字可能会被忽视等)?

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案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);
    

    【讨论】:

    • 这确实有效。但我不完全确定为什么。您能否详细说明为什么T extends string 因为Record 键满足只有字符串键的接口类型,但string 的纯Record 不满足?我还发现使用类型别名而不是接口是可行的。我发现github.com/microsoft/TypeScript/issues/15300 我猜是相关的,所以也许这就是它的样子......
    • 这是因为Record&lt;string, string&gt; — 与直觉相反 — 并不意味着只有字符串属性的对象。它只是意味着当这样的对象具有字符串属性时,它们的值也必须是字符串。但是,仍然允许这样的对象具有数字或符号属性。看看这个:github.com/Microsoft/TypeScript/issues/… 和这个:github.com/Microsoft/TypeScript/issues/…
    • 对于类型与接口——类型具有隐式索引属性。接口需要显式的。
    【解决方案2】:

    这是接口行为的known issue

    简单的解决方法是在这里使用类型别名而不是接口,如果不应该影响您未来的代码,因为它们(大部分)是可互换的。

    type Handlers = {
      foo: Handler<number, string>;
      bar: Handler<string, number>;
    }
    

    ts playground

    【讨论】:

      猜你喜欢
      • 2022-01-28
      • 1970-01-01
      • 2018-06-29
      • 1970-01-01
      • 2014-04-29
      • 1970-01-01
      • 1970-01-01
      • 2021-12-26
      相关资源
      最近更新 更多