精确扩展联合类型

时间:2019-06-19 20:23:52

标签: typescript

假设以下示例代码:

type Action = "GET" | "POST" | "PUT";
type Handler<A extends Action> = (action: A) => void;

const handlers: Partial<Record<Action, Handler<Action>>> = { };

function register<A extends Action>(action: A, handler: Handler<A>) {
    /*
    Error:
    Type 'Handler<A>' is not assignable to type 'Partial<Record<Action, Handler<Action>>>[A]'.
        Type 'Handler<A>' is not assignable to type 'Handler<Action>'.
            Type 'Action' is not assignable to type 'A'.
            'Action' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'Action'.
                Type '"GET"' is not assignable to type 'A'.
                '"GET"' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'Action'.
     */
    handlers[action] = handler;
}

据我所知,发生上述错误是因为A允许类型比Action大(例如,可能是Action | "DELETE"),而我的{{ 1}}记录仅允许handlersexactly联合类型。有几种方法可以解决此问题:

  • 内部降低Action。由于用户仍然可以将更大的类型传递到handler中,因此可以使编译器安静下来,而无需实际解决问题。此外,强制转换永远都不是理想的方法:)
  • 将函数具体设置为register。现在,这意味着register(action: Action, handler: Handler<Action>)action的类型不必相同,这可能导致运行时错误。

由于这些变通办法都不能完全解决问题,因此我有办法强制handleraction都使用相同的handler,同时也不允许{{ 1}}是否大于A


编辑:

我实际上发现了一个较小的最小repro,它给出了相同的错误:

A

其中return语句给出与上述相同的错误。这揭示了实际的问题:Action实际上可以比联合更小,因此您最终可能会传入一个处理少于预期情况的函数。

1 个答案:

答案 0 :(得分:1)

除非实施了thisthis,否则最好的解决方案是不存在的,所以将其强制转换。

A extends Action不允许A大于Action。不能为Action | "DELETE"A extends B表示AB的子类型,或者如果您具有类型A的值,它也必须也是类型B的。类型Action | "DELETE"的值不必是类型Action的值,因为它可能是"DELETE"

您可能希望将handlers定义为将键与值相关联的映射类型:const handlers: {[K in Action]?: Handler<K>} = {};

但是,由于上面第一句话中提到的原因,仍然存在编译器无法看到handlers[action]handler兼容的问题。在TypeScript中对correlated类型的支持不大,而且这种情况变得越来越糟,因为TS3.5通过索引访问获得了更多strict