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

重载方法autocomplete参数值的类型推断问题求助

问题:泛型方法参数自动补全异常

我正尝试为泛型方法subscribeToTelemetry的参数添加自动补全支持,方法的重载签名如下:

subscribeToTelemetry<T extends keyof TypeMap>(name: T): Stream<TypeMap[T]>;
subscribeToTelemetry<T extends NonNullable<{}>>(name: EntityName<T>): Stream<T>; 

核心需求:

  • 未指定泛型类型参数时,根据传入的name自动推断返回类型,同时name参数给出对应TypeMap的键作为提示(如['mavic2' | 'ranger2' | 'ranger1']),这部分已正常工作。
  • 指定泛型类型(如DroneTelemetry)时,name参数应仅提示对应类型的实体名称(如['mavic1' | 'mavic2' | 'mavic3']),但目前会显示所有名称(包括ranger系列),同时返回类型推断错误。

完整代码示例:

type DroneTelemetry = { kind: 'DroneTelemetry', batteryLevel: number, velocity: number, location: {latitude: number, longitude: number}}
type RangerTelemetry = { kind: 'RangerTelemetry', velocity: number, location: {latitude: number, longitude: number}}

////// 实体名称和类型映射将自动生成
const entityNames = {
    RangerTelemetry: ['ranger1', 'ranger2', 'ranger3'],
    DroneTelemetry: ['mavic1', 'mavic2', 'mavic3'],
} as const;

type TypeMap = {
    ranger1: RangerTelemetry,
    ranger2: RangerTelemetry,
    mavic2: DroneTelemetry
};
////////

// 定义映射的键和值类型
type EntityNames = typeof entityNames;
type EntityKeys = keyof EntityNames;
type EntityValues<T extends EntityKeys> = EntityNames[T][number];

// 定义类型别名到对应键的映射
type TypeToKey<T> = T extends { kind: infer K } ? K : never;

type ExtractEntitiesNames<T> = TypeToKey<T> extends keyof EntityNames ? EntityValues<TypeToKey<T>> : never;
type EntityName<T> = ExtractEntitiesNames<T> |  (string  & {});

/////////////////////////////////////////////////////////////////////

class Stream<T> {
    name: string
    constructor(name: string){
        this.name = name
    }
    getLastValue() {
        return {} as T // 模拟实现
    }
}

class Registry {
    subscribeToTelemetry<T extends keyof TypeMap>(name: T): Stream<TypeMap[T]>;
    subscribeToTelemetry<T extends NonNullable<{}>>(name: EntityName<T>): Stream<T>;
    subscribeToTelemetry<T extends NonNullable<{}>>(name: EntityName<T>): Stream<T> {
        return new Stream<T>(name); 
    }
}

const r = new Registry();
// 未指定类型T时应提示: ['mavic2' | 'ranger2' | 'ranger1'] // 正常
const myStream1 = r.subscribeToTelemetry('ranger1'); // myStream1类型为Stream<RangerTelemetry>

// 未指定类型T时应提示: ['mavic2' | 'ranger2' | 'ranger1'] // 正常
const myStream2 = r.subscribeToTelemetry('ranger2'); // myStream2类型为Stream<RangerTelemetry>

 // 指定类型T为DroneTelemetry时,应仅提示: ['mavic1' | 'mavic2' | 'mavic3']
 // 但实际显示了所有名称 ['mavic1' | 'mavic2' | 'mavic3', 'ranger1', 'ranger2', 'ranger3'] // 异常
const myStream3 = r.subscribeToTelemetry<DroneTelemetry>('mavic1'); // myStream3类型错误地推断为Stream<RangerTelemetry>

解决方案

问题出在重载签名的优先级和类型约束上,当显式指定泛型参数时,TypeScript优先匹配了第一个重载(基于TypeMap的签名),导致类型推断错误。调整重载顺序并优化类型映射逻辑即可解决:

type DroneTelemetry = { kind: 'DroneTelemetry', batteryLevel: number, velocity: number, location: {latitude: number, longitude: number}}
type RangerTelemetry = { kind: 'RangerTelemetry', velocity: number, location: {latitude: number, longitude: number}}

////// 实体名称和类型映射将自动生成
const entityNames = {
    RangerTelemetry: ['ranger1', 'ranger2', 'ranger3'],
    DroneTelemetry: ['mavic1', 'mavic2', 'mavic3'],
} as const;

type TypeMap = {
    ranger1: RangerTelemetry,
    ranger2: RangerTelemetry,
    mavic2: DroneTelemetry
};
////////

// 定义映射的键和值类型
type EntityNames = typeof entityNames;
type EntityKeys = keyof EntityNames;
type EntityValues<T extends EntityKeys> = EntityNames[T][number];

// 定义类型到实体名称的反向映射
type TypeToEntityNames<T> = 
    T extends RangerTelemetry ? EntityValues<'RangerTelemetry'> :
    T extends DroneTelemetry ? EntityValues<'DroneTelemetry'> :
    string & {};

// 定义类型到对应TypeMap键的映射(用于未指定泛型时的推断)
type NameToType<T extends keyof TypeMap> = TypeMap[T];

/////////////////////////////////////////////////////////////////////

class Stream<T> {
    name: string
    constructor(name: string){
        this.name = name
    }
    getLastValue() {
        return {} as T // 模拟实现
    }
}

class Registry {
    // 优先匹配显式指定泛型的情况
    subscribeToTelemetry<T>(name: TypeToEntityNames<T>): Stream<T>;
    // 未指定泛型时,根据name推断返回类型
    subscribeToTelemetry<T extends keyof TypeMap>(name: T): Stream<NameToType<T>>;
    subscribeToTelemetry<T>(name: string): Stream<T> {
        return new Stream<T>(name); 
    }
}

const r = new Registry();
// 未指定类型T时,提示TypeMap的键,返回类型正确
const myStream1 = r.subscribeToTelemetry('ranger1'); // Stream<RangerTelemetry>
const myStream2 = r.subscribeToTelemetry('ranger2'); // Stream<RangerTelemetry>
const myStream4 = r.subscribeToTelemetry('mavic2'); // Stream<DroneTelemetry>

// 指定类型T为DroneTelemetry时,仅提示mavic系列名称,返回类型正确
const myStream3 = r.subscribeToTelemetry<DroneTelemetry>('mavic1'); // Stream<DroneTelemetry>
const myStream5 = r.subscribeToTelemetry<DroneTelemetry>('mavic3'); // Stream<DroneTelemetry>
// 指定类型T为RangerTelemetry时,仅提示ranger系列名称
const myStream6 = r.subscribeToTelemetry<RangerTelemetry>('ranger3'); // Stream<RangerTelemetry>

关键改动说明:

  • 调整重载顺序:将显式指定泛型的重载放在前面,确保TypeScript优先匹配该场景。
  • 优化类型映射:创建TypeToEntityNames类型,直接将目标类型映射到对应的实体名称集合,避免间接推断的模糊性。
  • 明确自动推断逻辑:使用NameToType直接关联TypeMap的键和对应类型,保证未指定泛型时的推断准确性。

修改后两种场景均能正常工作:

  • 未指定泛型时,name提示TypeMap的所有键,返回类型自动匹配。
  • 显式指定泛型时,name仅提示对应类型的实体名称,返回类型与指定泛型一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 06:07:02