如果你只是想尽快跳过错误,你可以使用the any
type来禁用fn
的类型判断:
function test2<K extends keyof Type>(key: K, ...args: Parameters<Type[K]>) {
// TS can't figure out the relationship, just mark fn as any
const fn: any = o[key];
fn(...args); // okay
}
当然,您完全放弃编译器验证的类型安全是为了方便.当使用any
或type assertions时,你将类型判断的责任从编译器中剥离出来.
另一方面,如果您希望编译器理解您正在做的事情的逻辑,那么您需要将o
的类型重构为在类型级别上实际表示它的某种东西.类型判断器不知道Parameters<Type[K]>
对于类型Type[K]
的函数是合适的参数.类型Type[K]
被视为被约束到union个函数,而不是单个generic个函数类型.类型判断器不能将表单{ test(a: number, b: number, c: number): void; test2(): void }
抽象为任何有用的内容.类型Parameters<Type[K]>
是泛型conditional type,并且这样的类型对于编译器来说是出了名的难以理解.它没有将意义赋予识别符"Parameters
",也不能抽象地推理出这一点.
打字脚本不能推断数据 struct 之间的抽象相关性这一普遍问题在microsoft/TypeScript#30581中讨论过,在那里它是根据相关unions来表述的.对此支持的解决方案和推荐的方法是将通用indexed accesses的特定形式重构为mapped type.这在microsoft/TypeScript#47109中有详细的描述.
对于这里的示例代码,必要的重构如下所示
function test3<K extends keyof Type>(key: K, ...args: Parameters<Type[K]>) {
const _o: { [P in keyof Type]: (...args: Parameters<Type[P]>) => void } = o;
const fn = _o[key];
fn(...args); // okay
}
这里我们将o
赋值给映射类型{ [P in keyof Type]: (...args: Parameters<Type[P]>) => void }
的新变量_o
. 这个赋值成功是因为当给定一个非泛型值o
时,编译器会完全计算_o
的类型,并发现它们在 struct 上是相同的.
但即使它们在 struct 上是相同的,它们也有不同的表现形式.映射类型显式捕获任意属性_o
与其函数形状之间的泛型关系. 用key
索引到_o
并不被视为函数的联合,而是直接作为单个函数类型(...args: Parameters<Type[K]>) => void
. 这个函数类型很容易接受...args
.
这就是TypeScrip如何在保持一定的类型安全保证的情况下支持这种功能.这种特殊的重构不需要花费太多精力或添加代码,但通常这种重构会使需要维护代码的人类开发人员更难理解代码.这取决于您的用例,快速/轻松/不安全和缓慢/复杂/安全中的哪一个更可取.
Playground link to code个