TypeScript Parameters类型使用问题:泛型类约束不兼容报错
如何让ClassTwo中的ClassOne实例适配任意T类型的方法回调?
问题代码与报错
我编写了以下TypeScript代码,期望实现一个可接收任意方法回调的ClassOne实例,但遇到了类型约束错误:
class ClassOne<T extends (...args: any[]) => void> { callbacks = new Set<T>(); add(cb: T) { this.callbacks.add(cb); } trigger(...args: Parameters<T>) { this.callbacks.forEach(cb => cb(...args)); } } class ClassTwo<T extends Record<PropertyKey, (...args: any[]) => any>> { one = new ClassOne<(key: keyof T, ...args: Parameters<T[keyof T]>) => void>(); }
TypeScript抛出的错误信息:
Type '(key: keyof T, ...args: Parameters<T[keyof T]>) => void' does not satisfy the constraint '(...args: any[]) => void'. Types of parameters 'key' and 'args' are incompatible. Type 'any[]' is not assignable to type '[key: keyof T, ...args: Parameters<T[keyof T]>]'. Target requires 2 element(s) but source may have fewer.(2344)
我无法将回调签名改为(...args: any) => void(会触发Type 'Parameters<T>' must have a '[Symbol.iterator]()' method that returns an iterator.(2488)错误),也不能直接使用T[keyof T](因为需要将key作为第一个参数传入回调)。
解决方案
错误根源在于:Parameters<T[keyof T]>是T所有方法参数的联合类型,TypeScript无法确定它与key组合后的参数列表是否能被any[]兼容(联合类型的参数长度不固定,且原定义要求至少两个元素,而any[]可能为空)。
我们可以通过将回调类型定义为所有键对应的签名联合来解决这个问题:
class ClassOne<T extends (...args: any[]) => void> { callbacks = new Set<T>(); add(cb: T) { this.callbacks.add(cb); } trigger(...args: Parameters<T>) { this.callbacks.forEach(cb => cb(...args)); } } class ClassTwo<T extends Record<PropertyKey, (...args: any[]) => any>> { // 定义单个键对应的回调类型 type Callback<K extends keyof T> = (key: K, ...args: Parameters<T[K]>) => void; // 用联合类型覆盖所有键的回调场景 one = new ClassOne<Callback<keyof T>>(); }
代码说明
Callback<keyof T>会展开为T所有键对应的回调签名的联合。例如若T = { foo: (a: number) => void, bar: (b: string, c: boolean) => void },则联合类型为:((key: "foo", ...args: [number]) => void) | ((key: "bar", ...args: [string, boolean]) => void)- 这个联合类型完全满足
(...args: any[]) => void的约束,每个分支都是合法的可变参数函数。
进阶优化(可选)
如果需要确保trigger方法的参数能严格匹配每个回调,可以在ClassOne的trigger方法中添加类型断言(前提是你能保证传入的参数对应正确的key):
class ClassOne<T extends (...args: any[]) => void> { callbacks = new Set<T>(); add(cb: T) { this.callbacks.add(cb); } trigger(...args: Parameters<T>) { this.callbacks.forEach(cb => { // 类型断言确保联合类型的回调能接收传入的参数 (cb as (...args: any[]) => void)(...args); }); } } class ClassTwo<T extends Record<PropertyKey, (...args: any[]) => any>> { type Callback<K extends keyof T> = (key: K, ...args: Parameters<T[K]>) => void; one = new ClassOne<Callback<keyof T>>(); }
内容的提问来源于stack exchange,提问作者Evyatar Shafran
相关产品推荐
相关产品推荐

