Я пытаюсь добавить поддержку автозаполнения для параметра универсального метода.
subscribeToTelemetry<T extends keyof TypeMap>(name: T): Stream<TypeMap[T]>;
subscribeToTelemetry<T extends NonNullable<{}>>(name: EntityName<T>): Stream<T>;
Короче говоря, если тип не указан, тип возвращаемого значения должен быть выведен на основе имени и некоторых доступных внедренных/автогенерируемых типов. Кроме того, возможные предложения имен должны быть сделаны с использованием доступных внедренных/автогенерированных типов. Например, используя следующий тип:
type TypeMap = {
ranger1: RangerTelemetry,
ranger2: RangerTelemetry,
mavic2: DroneTelemetry
};
и следующий код
// should suggest: ['mavic2' | 'ranger2' | 'ranger1'] since no type T is provided
const myStream1 = r.subscribeToTelemetry('ranger1'); // myStream1: Stream<RangerTelemetry
Рекомендуемое значение должно быть ['mavic2' | 'рейнджер2' | 'ranger1'], поскольку тип T не указан, а тип myStream1 будет subscribeToTelemetry
Напротив, если мы предоставим тип Stream<RangerTelemetry>
и учитывая доступный внедренный/автогенерированный тип
const entityNames = {
RangerTelemetry: ['ranger1', 'ranger2', 'ranger3'],
DroneTelemetry: ['mavic1', 'mavic2', 'mavic3'],
} as const;
и некоторые другие трюки типа (см. ссылку на игровую площадку), используя следующий код
// should suggest only: ['mavic1' | 'mavic2' | 'mavic3'] since type T is provided as DroneTelemetry,
const myStream3 = r.subscribeToTelemetry<DroneTelemetry>('mavic1');
Мы должны предложить: ['mavic1' | 'мавик2' | 'mavic3'], так как был указан тип subscribeToTelemetry
, но ['mavic1' | 'мавик2' | Вместо этого отображаются «mavic3», «ranger1», «ranger2», «ranger3»]. Тип DroneTelemetry
будет myStream3
У меня есть несколько вариантов реализации подписи перегруженного метода Stream<DroneTelemetry>
, но мне не удается предоставить правильные предложения при предоставлении типа. Я всегда получаю предложение рейнджеров, даже когда указываю subscribeToTelemetry
в качестве типа.
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);
}
}
Если у кого-нибудь есть предложения, буду признателен за любую помощь
Умберто
Вот полный код:
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>
Вот тс-площадка с полным кодом
• NonNullable<{}>
— это просто {}
; пожалуйста, отредактируйте , чтобы просто использовать {}
, поскольку это в лучшем случае отвлекает. • Ваша проблема: ms/TS#26892 , и вы получаете предложения от обеих перегрузок. Обходной путь, который я бы предложил, — избегать перегрузок и вместо этого использовать общие условные типы, как показано в этой ссылке на игровую площадку. Это полностью решает вопрос? Если да, то я напишу ответ; если нет, то что мне не хватает?
@jcalz Я заглянул в вашу игровую площадку, и она почти идеальна, проблема в том, что, когда тип не указан, предполагаемым типом является Stream<'mavic2'> вместо Stream<DroneTelemetry>. Спасибо за помощь, постараюсь все исправить 👍🏼
Извините, посмотрите ссылку на эту игровую площадку. Это работает сейчас? Стоит ли писать ответ или я что-то еще упускаю?
привет @jcalz, извини за задержку, обязанности отца. Я только что проверил ваше последнее решение, все работает как положено 👌. Вы можете написать ответ
Давайте продолжим обсуждение в чате.
Нет, спасибо, я не хочу продолжать обсуждение этого вопроса, или, по крайней мере, не вести его в полуприватном чате здесь.
Мы можем исправить это, изменив подход к сопоставлению типов друг с другом. Это будет лучше видно в коде, чем объяснено. При таком подходе вам не нужно перегружать функцию.
Краткое изложение подхода:
TypeMap
(с Record<> or {[x in key]: value}
, где имена дронов имеют значение DroneTelemetry
и то же самое для имен рейнджеров).subscribeToTelemetry
T
— тип сущности DroneTelemetry|RangerTelemetry
F
— это имя сущности, основанное на T
, которое, если его не сузить, ни по определению, ни по умозаключению, будет именами всех сущностей. Когда он сужается с помощью проходного T
, благодаря EntityName
к типу объекта относятся только ключи. Это полезно не только для автозаполнения параметров, но и для типа возвращаемого значения, как указано в следующем пункте.F
TypeMap
, чтобы получить возвращенный EntityType
T
не пройден. Значение F
будет выведено, что приведет к соответствующему типуT
, мы не хотим передавать F
(слишком сумбурно), поэтому у нас есть тип по умолчанию, который сужается только до имен, относящихся к этому типу сущности, а это означает, что мы сузили ключ, который мы перейти к TypeMap
, вернув соответствующий EntityType
Следует отметить одну вещь: второй универсальный тип F выводится, когда мы не передаем T, но когда мы передаем T, мы должны передать F, потому что вы не можете передать некоторые общие типы и вывести остальные, либо все, либо ничего. . Вот почему нам нужно задать F
тип по умолчанию.
type DroneTelemetry = { kind: 'DroneTelemetry', batteryLevel: number, velocity: number, location: { latitude: number, longitude: number } }
type RangerTelemetry = { kind: 'RangerTelemetry', velocity: number, location: { latitude: number, longitude: number } }
type EntityType = DroneTelemetry | RangerTelemetry;
const entityNames = {
RangerTelemetry: ['ranger1', 'ranger2', 'ranger3'],
DroneTelemetry: ['mavic1', 'mavic2', 'mavic3'],
} as const;
type EntityNameMap = typeof entityNames;
type EntityName<T extends EntityType> = EntityNameMap[T['kind']][number]
type TypeMap = Record<EntityName<RangerTelemetry>, RangerTelemetry> &
Record<EntityName<DroneTelemetry>, DroneTelemetry>
class Stream<T extends EntityType> {
name: string
constructor(name: EntityName<T>) {
this.name = name
}
getLastValue() {
return {} as T // dummy impl
}
}
class Registry {
subscribeToTelemetry<T extends EntityType, F extends EntityName<T> = EntityName<T>>(name: F): Stream<TypeMap[F]> {
return new Stream<TypeMap[F]>(name);
}
}
const r = new Registry();
// Name options are all entity names
const myStream1 = r.subscribeToTelemetry('mavic2');
// ^?const myStream2: Stream<DroneTelemetry>
const myStream2 = r.subscribeToTelemetry('ranger2');
// ^?const myStream2: Stream<RangerTelemetry>
// Options are only drone names
const myStream3 = r.subscribeToTelemetry<DroneTelemetry>('mavic2');
// ^?const myStream3: Stream<DroneTelemetry>
Здравствуйте @Brother58697, спасибо за ваше время и решение. Интересный подход, который вы использовали 👌, он упрощает код.
В любое время @Умберто :)
Привет @jcalz, спасибо за твои предложения. Я включил исходный код минимального примера моей проблемы. В комментариях вы можете увидеть ожидаемое поведение и то, что я получаю.