TypeScript泛型父类调用trigger时的类型错误及解决
问题描述
想要创建一个自带若干钩子(hook)的基类,允许子类通过泛型扩展额外钩子且不修改原有钩子。相关TypeScript代码如下:
interface IHookable<THooks extends object> { trigger<THook extends keyof THooks>(hook: THook, parameters: THooks[THook]): void; } interface MainClass<TExtendedHooks extends object = {}> extends IHookable<{ 'firstHook': [MainClass], 'secondHook': [MainClass] } & TExtendedHooks> {} class MainClass<TExtendedHooks extends object = {}> { innerFunction() { this.trigger('firstHook', [this]); } } /** The trigger function will be added to the MainClass with a mixin here, but it is ommited for brevity */ class ExtendingClass extends MainClass<{ 'thirdHook': [ExtendingClass] }> { otherInnerFunction() { this.trigger('firstHook', [this]); this.trigger('thirdHook', [this]); } }
代码运行正常,但TypeScript在基类中报错:
Argument of type '[this]' is not assignable to parameter of type '[MainClass<{}>] & TExtendedHooks["firstHook"]'. Type '[this]' is not assignable to type 'TExtendedHooks["firstHook"]'.(2345)
推测错误原因是TypeScript无法确定泛型参数TExtendedHooks不会覆盖或扩展基类预定义的钩子,导致基类内部调用trigger时类型校验失败。
解决方案
核心思路是限制泛型参数TExtendedHooks不能包含基类已有的钩子键,让TypeScript明确基类钩子不会被覆盖,从而消除类型错误。具体修改如下:
方案1:基础约束版本
// 抽离基类自带的钩子类型,便于复用和约束 type BaseHooks = { 'firstHook': [MainClass<any>], 'secondHook': [MainClass<any>] }; interface IHookable<THooks extends object> { trigger<THook extends keyof THooks>(hook: THook, parameters: THooks[THook]): void; } // 约束TExtendedHooks不能包含BaseHooks中的键 interface MainClass<TExtendedHooks extends Omit<object, keyof BaseHooks> = {}> extends IHookable<BaseHooks & TExtendedHooks> {} class MainClass<TExtendedHooks extends Omit<object, keyof BaseHooks> = {}> { innerFunction() { // 此时TypeScript能确定firstHook的参数类型就是BaseHooks定义的,无冲突 this.trigger('firstHook', [this]); } } /** 混入trigger逻辑(省略) */ class ExtendingClass extends MainClass<{ 'thirdHook': [ExtendingClass] }> { otherInnerFunction() { this.trigger('firstHook', [this]); this.trigger('thirdHook', [this]); } }
方案2:精准类型版本(推荐)
如果需要基类钩子的参数类型匹配当前实例的泛型类型,可以将BaseHooks改为泛型:
type BaseHooks<T> = { 'firstHook': [T], 'secondHook': [T] }; interface IHookable<THooks extends object> { trigger<THook extends keyof THooks>(hook: THook, parameters: THooks[THook]): void; } interface MainClass<TExtendedHooks extends Omit<object, keyof BaseHooks<any>> = {}> extends IHookable<BaseHooks<MainClass<TExtendedHooks>> & TExtendedHooks> {} class MainClass<TExtendedHooks extends Omit<object, keyof BaseHooks<any>> = {}> { innerFunction() { this.trigger('firstHook', [this]); } } /** 混入trigger逻辑(省略) */ class ExtendingClass extends MainClass<{ 'thirdHook': [ExtendingClass] }> { otherInnerFunction() { this.trigger('firstHook', [this]); this.trigger('thirdHook', [this]); } }
说明
- 通过
Omit<object, keyof BaseHooks>约束TExtendedHooks,强制子类不能传入基类已有的钩子键,从根源上避免了类型冲突。 - 抽离
BaseHooks类型提升了代码复用性,让泛型约束逻辑更清晰。 - 方案2的泛型
BaseHooks能让基类钩子的参数类型与当前实例完全匹配,进一步提升类型校验的精准度。
内容的提问来源于stack exchange,提问作者Adam Baranyai
相关产品推荐
相关产品推荐

