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

使用泛型时出现类型不匹配问题(Discord.js+TypeScript)

解决Discord.js + TypeScript组件泛型类型不匹配问题

问题场景

我使用Discord.js和TypeScript开发Discord机器人,为快速创建可添加至消息的组件,编写了ComponentPrototype抽象类(注:ComponentGenerators是ButtonBuilder等Discord构建器的自定义子类),代码如下:

import { Awaitable, ButtonInteraction, StringSelectMenuInteraction, BaseInteraction} from "discord.js"
import { Logger, MyClient, ComponentGenerators } from "./index.js"
import { ButtonGenerator, StringSelectGenerator } from "../utils/index.js";

export interface CallbackProps<Interaction extends BaseInteraction | null> {
    interaction: Interaction,
    client: MyClient,
    log: Logger,
}

export type Callback<ReturnType, Interaction extends BaseInteraction | null, Args extends unknown[] = unknown[]> = (
    props: CallbackProps<Interaction>,
    ...args: Args
) => ReturnType;

export type VisibilityCallback = Callback<boolean, BaseInteraction | null>

type InteractionGeneratorMap<ComponentGenerator extends ComponentGenerators> = 
    ComponentGenerator extends ButtonGenerator ? ButtonInteraction : 
    ComponentGenerator extends StringSelectGenerator ?  StringSelectMenuInteraction :
    never;

export interface ComponentProtoData<Generator extends ComponentGenerators> {
    id: string,
    generator: Generator,
    visibilityCallback?: VisibilityCallback,
    callback?: Callback<Awaitable<unknown>, InteractionGeneratorMap<Generator>>
} 

export abstract class ComponentPrototype<Generator extends ComponentGenerators> implements ComponentProtoData<Generator>{

    public readonly id: string;

    public readonly generator: Generator;

    public readonly visibilityCallback: VisibilityCallback

    public readonly callback?: Callback<Awaitable<unknown>, InteractionGeneratorMap<Generator>>

    constructor(data: ComponentProtoData<Generator>) {
        this.id = data.id
        // ! set custom_id to id
        this.generator = data.generator
        this.generator.setCustomId(data.id)
        this.visibilityCallback = data.visibilityCallback ?? (() => true )
        this.callback = data.callback
    }
}

接着实现了Button子类:

export class Button extends ComponentPrototype<ButtonGenerator> {
    constructor(data: ComponentProtoData<ButtonGenerator>) {
        super(data)
    }
}

尝试创建容纳所有组件的数组时,出现类型不匹配错误:

const components: ComponentPrototype<ComponentGenerators>[] = [new Button({id: "test", generator: ButtonGenerator.Back()})]

错误信息:

Type 'Button' is not assignable to type 'ComponentPrototype<ComponentGenerators>'.
  Types of property 'callback' are incompatible.
    Type 'Callback<unknown, ButtonInteraction<CacheType>, unknown[]> | undefined' is not assignable to type 'Callback<unknown, ButtonInteraction<CacheType> | StringSelectMenuInteraction<CacheType>, unknown[]> | undefined'.
      Type 'Callback<unknown, ButtonInteraction<CacheType>, unknown[]>' is not assignable to type 'Callback<unknown, ButtonInteraction<CacheType> | StringSelectMenuInteraction<CacheType>, unknown[]>'.
        Types of parameters 'props' and 'props' are incompatible.
          Type 'CallbackProps<ButtonInteraction<CacheType> | StringSelectMenuInteraction<CacheType>>' is not assignable to type 'CallbackProps<ButtonInteraction<CacheType>>'.
            Type 'ButtonInteraction<CacheType> | StringSelectMenuInteraction<CacheType>' is not assignable to type 'ButtonInteraction<CacheType>'.
              Type 'StringSelectMenuInteraction<CacheType>' is not assignable to type 'ButtonInteraction<CacheType>'.ts(2322)

解决方案

方案1:使用联合类型定义数组(推荐)

问题核心是ComponentPrototype<ComponentGenerators>期望回调能接受ButtonInteraction | StringSelectMenuInteraction类型,但Button类的回调只接受ButtonInteraction,类型逆变导致不兼容。直接用组件类型的联合定义数组,既能保留类型安全,又能解决匹配问题:

// 定义所有组件类型的联合
type AnyComponent = ComponentPrototype<ButtonGenerator> | ComponentPrototype<StringSelectGenerator>;

// 使用联合类型数组
const components: AnyComponent[] = [new Button({id: "test", generator: ButtonGenerator.Back()})];

方案2:重构泛型结构,基于交互类型约束

将ComponentPrototype的泛型参数从生成器类型改为交互类型,让数组可以统一约束到基础交互类型,同时保留子类的类型安全:

import { Awaitable, ButtonInteraction, StringSelectMenuInteraction, BaseInteraction} from "discord.js"
import { Logger, MyClient, ComponentGenerators } from "./index.js"
import { ButtonGenerator, StringSelectGenerator } from "../utils/index.js";

export interface CallbackProps<Interaction extends BaseInteraction | null> {
    interaction: Interaction,
    client: MyClient,
    log: Logger,
}

export type Callback<ReturnType, Interaction extends BaseInteraction | null, Args extends unknown[] = unknown[]> = (
    props: CallbackProps<Interaction>,
    ...args: Args
) => ReturnType;

export type VisibilityCallback = Callback<boolean, BaseInteraction | null>

// 生成器到交互类型的映射
type GeneratorToInteraction<G> = 
    G extends ButtonGenerator ? ButtonInteraction : 
    G extends StringSelectGenerator ? StringSelectMenuInteraction :
    never;

// 重构ComponentPrototype,泛型参数为交互类型
export abstract class ComponentPrototype<I extends BaseInteraction> {
    public readonly id: string;
    public readonly generator: ComponentGenerators;
    public readonly visibilityCallback: VisibilityCallback;
    public readonly callback?: Callback<Awaitable<unknown>, I>;

    constructor<G extends ComponentGenerators>(data: {
        id: string;
        generator: G;
        visibilityCallback?: VisibilityCallback;
        callback?: Callback<Awaitable<unknown>, GeneratorToInteraction<G>>;
    }) {
        this.id = data.id;
        this.generator = data.generator;
        this.generator.setCustomId(data.id);
        this.visibilityCallback = data.visibilityCallback ?? (() => true);
        this.callback = data.callback as Callback<Awaitable<unknown>, I>;
    }
}

// 重构Button类
export class Button extends ComponentPrototype<ButtonInteraction> {
    constructor(data: {
        id: string;
        generator: ButtonGenerator;
        visibilityCallback?: VisibilityCallback;
        callback?: Callback<Awaitable<unknown>, ButtonInteraction>;
    }) {
        super(data);
    }
}

// 数组可以直接使用基础交互类型约束
const components: ComponentPrototype<BaseInteraction>[] = [new Button({id: "test", generator: ButtonGenerator.Back()})];

方案3:类型断言(临时方案,不推荐)

如果只是临时解决问题,可以用类型断言跳过类型检查,但会丢失部分类型安全:

const components: ComponentPrototype<ComponentGenerators>[] = [
    new Button({id: "test", generator: ButtonGenerator.Back()}) as ComponentPrototype<ComponentGenerators>
];

内容的提问来源于stack exchange,提问作者Entropy A

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:25:54