精确扩展联合类型

y2b*_*2bd 7 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;
}
Run Code Online (Sandbox Code Playgroud)

据我了解,发生上述错误是因为A允许类型大于Action(例如可能是Action | "DELETE"),但我的handlers记录只允许exactly联合Action类型。我可以通过一些方法来解决这个问题:

  • 内心沮丧handler。这会使编译器安静下来,但实际上并没有解决问题,因为用户仍然可以将更大的类型传递给register. 另外,演员阵容从来都不是理想的:)
  • 使功能具体化,register(action: Action, handler: Handler<Action>). 现在这意味着 和action不必handler就它们的类型达成一致,这可能会导致运行时错误。

由于这些解决方法都不能完全解决问题,有没有办法让我强制执行action并且handler都使用相同的A,同时也不允许A大于Action


编辑:

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

function foobar<T extends "foo" | "bar">(func: (arg: T) => void): (arg: "foo" | "bar") => void {
    return func;
}
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*isW 5

除非实现了这个这个,否则就不存在一个好的解决方案,所以只需投射它即可。

A extends Action不允许A有任何比 更大的东西Action。它不可能是Action | "DELETE"A extends B意味着它A是 的子类型B,或者如果您有一个类型的值,A它也必须是 类型的B。type 的值Action | "DELETE"不需要是 type 的值Action,因为它可能是"DELETE"

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

但由于上面第一句中提到的原因,编译器看不到这一点handlers[action]并且兼容的问题仍然存在。TypeScript 中对相关handler类型的支持并不多,而且自从 TS3.5对索引访问变得更加严格以来,这种情况只会变得更糟。