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

TypeScript中Record与泛型结合时satisfies条件不生效问题

问题描述

此问题关联另一问题:如何在TypeScript中动态存储“类型”并实例化

尝试在代码中加入泛型与抽象基类后,VSCode提示大量错误,调整satisfies条件也无效。

最小复现代码

export enum CommandType {
    Click = 'Click',
    NewTab = 'NewTab',
}

export interface AutomationCommand {
    id: string;
    type: CommandType;
}

export interface TabAutomationCommand extends AutomationCommand {
    id: string;
    type: CommandType;
    tabId: number
}

export abstract class SelectorCommand implements TabAutomationCommand {
    id: string = '';

    constructor(public type: CommandType, public tabId: number, public selector: string) { }
}

export class ClickCommand extends SelectorCommand {
    constructor(tabId: number, selector: string) {
        super(CommandType.Click, tabId, selector);
    }
}

export class NewTabCommand implements AutomationCommand {
    type: CommandType = CommandType.NewTab;
    id: string = '';

    constructor(public url?: string) { }
}

export class AutomationResponse<TResponse> {
    constructor(public id: string, public data: TResponse) { }
}

export interface CommandHandler<TCommand extends AutomationCommand, TResponse> {
    handle(command: TCommand): Promise<AutomationResponse<TResponse>>;
}

export abstract class TabCommandHandler<TCommand extends TabAutomationCommand, TResponse> implements CommandHandler<TCommand, TResponse> {
    constructor() { }

    abstract handle(command: TCommand): Promise<AutomationResponse<TResponse>>;
}

export class ClickCommandHandler extends TabCommandHandler<ClickCommand, boolean> {

    async handle(command: ClickCommand): Promise<AutomationResponse<boolean>> {
       throw Error('not implemented');
    }
}

export class NewTabCommandHandler implements CommandHandler<NewTabCommand, number>{
    constructor() { }

    async handle(command: NewTabCommand): Promise<AutomationResponse<number>> {
       throw Error('not implemented');
    }
}

//BELOW DOES NOT WORK
const handlers = {
        [CommandType.Click]: ClickCommandHandler,
        [CommandType.NewTab]: NewTabCommandHandler,
    } satisfies Record<CommandType, (new <TCommad extends AutomationCommand, TResponse>() => CommandHandler<TCommad, TResponse>)>;

async function execute<TCommand extends AutomationCommand, TResponse>(command: TCommand): Promise<AutomationResponse<TResponse>> {

        const handlerType = handlers[command.type];
        if (!handlerType)
            throw Error(`No handler found for command type '${command.type}'`)

        const handler = new handlerType();
        return await handler.handle(command);
    }

错误信息

Type of computed property's value is 'typeof ClickCommandHandler', which is not assignable to type 'new <TCommad extends AutomationCommand, TResponse>(pageAwaiter: Awaiter, tabs: Map<number, TabSession>) => CommandHandler<TCommad, TResponse>'.
  Types of construct signatures are incompatible.
    Type 'new (pageAwaiter: Awaiter, tabs: Map<number, TabSession>) => ClickCommandHandler' is not assignable to type 'new <TCommad extends AutomationCommand, TResponse>(pageAwaiter: Awaiter, tabs: Map<number, TabSession>) => CommandHandler<TCommad, TResponse>'.
      Construct signature return types 'ClickCommandHandler' and 'CommandHandler<TCommad, TResponse>' are incompatible.
        The types of 'handle' are incompatible between these types.
          Type '(command: ClickCommand) => Promise<AutomationResponse<boolean>>' is not assignable to type '(command: TCommad) => Promise<AutomationResponse<TResponse>>'.
            Types of parameters 'command' and 'command' are incompatible.
              Type 'TCommad' is not assignable to type 'ClickCommand'.
                Type 'AutomationCommand' is missing the following properties from type 'ClickCommand': tabId, selector

这应该是个简单问题,我忽略了什么?


解决方案

问题出在handlers的类型约束上:你用的泛型构造签名要求每个处理类能生成任意泛型的CommandHandler,但实际每个处理类只对应特定的命令和响应类型,类型不匹配导致报错。

以下是修正后的完整代码,核心是建立命令类型与处理类的精准映射,让TypeScript能正确推断关联关系:

export enum CommandType {
    Click = 'Click',
    NewTab = 'NewTab',
}

export interface AutomationCommand {
    id: string;
    type: CommandType;
}

export interface TabAutomationCommand extends AutomationCommand {
    id: string;
    type: CommandType;
    tabId: number
}

export abstract class SelectorCommand implements TabAutomationCommand {
    id: string = '';

    constructor(public type: CommandType, public tabId: number, public selector: string) { }
}

export class ClickCommand extends SelectorCommand {
    constructor(tabId: number, selector: string) {
        super(CommandType.Click, tabId, selector);
    }
}

export class NewTabCommand implements AutomationCommand {
    type: CommandType = CommandType.NewTab;
    id: string = '';

    constructor(public url?: string) { }
}

export class AutomationResponse<TResponse> {
    constructor(public id: string, public data: TResponse) { }
}

export interface CommandHandler<TCommand extends AutomationCommand, TResponse> {
    handle(command: TCommand): Promise<AutomationResponse<TResponse>>;
}

export abstract class TabCommandHandler<TCommand extends TabAutomationCommand, TResponse> implements CommandHandler<TCommand, TResponse> {
    constructor() { }

    abstract handle(command: TCommand): Promise<AutomationResponse<TResponse>>;
}

export class ClickCommandHandler extends TabCommandHandler<ClickCommand, boolean> {
    async handle(command: ClickCommand): Promise<AutomationResponse<boolean>> {
       throw Error('not implemented');
    }
}

export class NewTabCommandHandler implements CommandHandler<NewTabCommand, number>{
    constructor() { }

    async handle(command: NewTabCommand): Promise<AutomationResponse<number>> {
       throw Error('not implemented');
    }
}

// 1. 定义命令类型到处理类的精准映射
type HandlerMap = {
    [CommandType.Click]: typeof ClickCommandHandler;
    [CommandType.NewTab]: typeof NewTabCommandHandler;
};

// 2. 建立命令、处理类、响应类型的关联
type CommandAssociation<T extends CommandType> = 
    T extends CommandType.Click ? { command: ClickCommand; response: boolean } :
    T extends CommandType.NewTab ? { command: NewTabCommand; response: number } :
    never;

// 3. 让handlers符合映射类型,移除错误的泛型约束
const handlers: HandlerMap = {
    [CommandType.Click]: ClickCommandHandler,
    [CommandType.NewTab]: NewTabCommandHandler,
};

// 4. 调整execute函数,基于命令类型推断正确的响应类型
async function execute<T extends CommandType>(command: CommandAssociation<T>['command']): Promise<AutomationResponse<CommandAssociation<T>['response']>> {
    const handlerType = handlers[command.type];
    if (!handlerType)
        throw Error(`No handler found for command type '${command.type}'`);

    const handler = new handlerType();
    // 类型断言:告诉TS当前handler能处理传入的命令
    return await (handler as CommandHandler<typeof command, CommandAssociation<T>['response']>).handle(command);
}

关键修改说明

  1. HandlerMap:明确每个命令类型对应的处理类构造函数,替代之前错误的泛型约束,让TS知道每个键值对的精准类型。
  2. CommandAssociation:通过条件类型建立命令类型、具体命令类、响应类型的绑定,确保execute函数能根据传入的命令自动推断出返回的响应类型。
  3. execute函数泛型调整:不再使用任意的TCommand和TResponse,而是基于CommandType做关联,保证类型安全。
  4. 类型断言:由于TS无法自动从HandlerMap推断出处理类的具体泛型参数,需要用断言明确当前handler能处理传入的命令。

修改后,TS能正确检查类型,动态实例化处理类的功能也得以保留。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 19:45:55