You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 04:27:10