【发布时间】:2019-11-02 15:30:06
【问题描述】:
假设如下示例代码:
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"),但我的handlers 记录只允许@ 987654331@ 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;
}
return 语句给出与上述相同的错误。这揭示了实际问题:T 实际上可能比联合小,因此您最终可能会传入一个可以处理比预期更少的情况的函数。
【问题讨论】:
-
A extends Action不允许A比Action“大”。它不能是Action | "DELETE".A extends B` 意味着A是B的子类型,或者如果你有一个A类型的值,它也必须是@987654352 类型@。Action | "DELETE"类型的值不需要是Action类型的值,因为它可能是"DELETE"。 -
您可能希望将
handlers定义为将键与值相关联的映射类型:const handlers: {[K in Action]?: Handler<K>} = {}; -
但是由于@Austaras 提到的原因,编译器无法看到
handlers[action]和handler兼容的问题仍然存在。 TypeScript 中对 correlated 类型的支持不是很好,而且这种情况只会变得更糟,因为 TS3.5 通过索引访问获得了更多的 strict。 -
看看this answer,它试图完全解释这种信息的含义。让我知道它是否能解决您的问题。
标签: typescript