假设以下示例代码:
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}}记录仅允许handlers
和exactly
联合类型。有几种方法可以解决此问题:
Action
。由于用户仍然可以将更大的类型传递到handler
中,因此可以使编译器安静下来,而无需实际解决问题。此外,强制转换永远都不是理想的方法:)register
。现在,这意味着register(action: Action, handler: Handler<Action>)
和action
的类型不必相同,这可能导致运行时错误。由于这些变通办法都不能完全解决问题,因此我有办法强制handler
和action
都使用相同的handler
,同时也不允许{{ 1}}是否大于A
?
编辑:
我实际上发现了一个较小的最小repro,它给出了相同的错误:
A
其中return语句给出与上述相同的错误。这揭示了实际的问题:Action
实际上可以比联合更小,因此您最终可能会传入一个处理少于预期情况的函数。
答案 0 :(得分:1)
除非实施了this或this,否则最好的解决方案是不存在的,所以将其强制转换。
A extends Action
不允许A
大于Action
。不能为Action | "DELETE"
。 A extends B
表示A
是B
的子类型,或者如果您具有类型A
的值,它也必须也是类型B
的。类型Action | "DELETE"
的值不必是类型Action
的值,因为它可能是"DELETE"
。
您可能希望将handlers
定义为将键与值相关联的映射类型:const handlers: {[K in Action]?: Handler<K>} = {};
但是,由于上面第一句话中提到的原因,仍然存在编译器无法看到handlers[action]
和handler
兼容的问题。在TypeScript中对correlated类型的支持不大,而且这种情况变得越来越糟,因为TS3.5通过索引访问获得了更多strict。