使用泛型时出现类型不匹配问题(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
相关产品推荐
相关产品推荐

