Estou tentando adicionar algum suporte de preenchimento automático para um parâmetro de método genérico
subscribeToTelemetry<T extends keyof TypeMap>(name: T): Stream<TypeMap[T]>;
subscribeToTelemetry<T extends NonNullable<{}>>(name: EntityName<T>): Stream<T>;
Em poucas palavras, se nenhum tipo for fornecido, subscribeToTelemetry
o tipo de retorno de deve ser inferido com base no nome e em alguns tipos injetados/gerados automaticamente disponíveis. Além disso, possíveis sugestões de nomes devem ser feitas usando os tipos injetados/gerados automaticamente disponíveis. Por exemplo, usando o seguinte tipo:
type TypeMap = {
ranger1: RangerTelemetry,
ranger2: RangerTelemetry,
mavic2: DroneTelemetry
};
e o seguinte código
// should suggest: ['mavic2' | 'ranger2' | 'ranger1'] since no type T is provided
const myStream1 = r.subscribeToTelemetry('ranger1'); // myStream1: Stream<RangerTelemetry
O valor sugerido deve ser ['mavic2' | 'ranger2' | 'ranger1'] já que nenhum tipo T é fornecido, e o tipo myStream1 seráStream<RangerTelemetry>
Pelo contrário, se fornecermos um tipo subscribeToTelemetry
e considerarmos o tipo injetado/gerado automaticamente disponível
const entityNames = {
RangerTelemetry: ['ranger1', 'ranger2', 'ranger3'],
DroneTelemetry: ['mavic1', 'mavic2', 'mavic3'],
} as const;
e alguns outros tipos de trics (veja o link do playground), usando o seguinte código
// should suggest only: ['mavic1' | 'mavic2' | 'mavic3'] since type T is provided as DroneTelemetry,
const myStream3 = r.subscribeToTelemetry<DroneTelemetry>('mavic1');
Deveríamos sugerir: ['mavic1' | 'mavic2' | 'mavic3'] já que o tipo DroneTelemetry
foi fornecido, mas ['mavic1' | 'mavic2' | 'mavic3', 'ranger1', 'ranger2', 'ranger3'] é mostrado em vez disso. O tipo de myStream3
seráStream<DroneTelemetry>
Tenho várias variantes para a implementação da assinatura do subscribeToTelemetry
método sobrecarregado, mas não consigo fornecer as sugestões corretas ao fornecer um tipo. Sempre recebo a sugestão dos rangers, mesmo quando especifico DroneTelemetry
como tipo.
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);
}
}
Se alguém tiver alguma sugestão, agradeceria qualquer ajuda
Humberto
Aqui está o código completo:
type DroneTelemetry = { kind: 'DroneTelemetry', batteryLevel: number, velocity: number, location: {latitude: number, longitude: number}}
type RangerTelemetry = { kind: 'RangerTelemetry', velocity: number, location: {latitude: number, longitude: number}}
////// the entity names and type mapping will be autogenerated
const entityNames = {
RangerTelemetry: ['ranger1', 'ranger2', 'ranger3'],
DroneTelemetry: ['mavic1', 'mavic2', 'mavic3'],
} as const;
type TypeMap = {
ranger1: RangerTelemetry,
ranger2: RangerTelemetry,
mavic2: DroneTelemetry
};
////////
// Define the types for the keys and values of the mapping
type EntityNames = typeof entityNames;
type EntityKeys = keyof EntityNames;
type EntityValues<T extends EntityKeys> = EntityNames[T][number];
// Define a mapping from type aliases to their corresponding keys
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 // dummy impl
}
}
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();
// should suggest: ['mavic2' | 'ranger2' | 'ranger1'] since no type T is provided // OK
const myStream1 = r.subscribeToTelemetry('ranger1'); // myStream1 type will be Stream<RangerTelemetry>
// should suggest: ['mavic2' | 'ranger2' | 'ranger1'] since no type T is provided // OK
const myStream2 = r.subscribeToTelemetry('ranger2'); // myStream2 type will be Stream<RangerTelemetry>
// should suggest only: ['mavic1' | 'mavic2' | 'mavic3'] since type T is provided as DroneTelemetry,
// but ['mavic1' | 'mavic2' | 'mavic3', 'ranger1', 'ranger2', 'ranger3'] is shown instead. // WRONG
const myStream3 = r.subscribeToTelemetry<DroneTelemetry>('mavic1'); // myStream3 type will be Stream<RangerTelemetry>
Aqui está um ts-playground com o código completo
Podemos consertar isso mudando a abordagem de como mapeamos os tipos uns aos outros. Isso será melhor visto no código do que explicado. Com essa abordagem, você não precisa sobrecarregar sua função.
Um resumo da abordagem:
TypeMap
(comRecord<> or {[x in key]: value}
onde os nomes dos drones têm um valor deDroneTelemetry
e o mesmo para os nomes dos rangerssubscribeToTelemetry
T
é o tipo de entidadeDroneTelemetry|RangerTelemetry
F
é o nome da entidade com base emT
, que quando não estreitado, seja por definição ou inferência, serão todos os nomes de entidade. Quando é estreitado por umT
, são apenas as chaves que pertencem ao tipo de entidade graças aEntityName
. Isso não é útil apenas para o autocompletar de parâmetros, mas para o tipo de retorno para, conforme o próximo ponto.F
para oTypeMap
para obter o retornoEntityType
T
não for passado,F
o valor de será inferido, levando ao tipo correspondenteT
for passado, não queremos passarF
(muito agitado), então temos um tipo padrão que se restringe apenas aos nomes que pertencem a esse tipo de entidade, o que significa que restringimos a chave que passamos paraTypeMap
, retornando o relevanteEntityType
Uma coisa a notar, o segundo tipo genérico F é inferido quando não passamos T, mas quando passamos T, temos que passar F, porque você não pode passar alguns tipos genéricos e inferir o resto, é tudo ou nada. É por isso que precisamos dar
F
um tipo padrão.Parque infantil