Вывод параметров общего типа в TypeScript на нескольких уровнях - PullRequest
0 голосов
/ 20 октября 2018

У меня есть следующий общий сценарий в моем коде TypeScript:

interface TopLevelInterface<A extends BottomLevelInterface<B>, B>

и реализация для BottomLevelInterface, которая выглядит следующим образом:

class BottomLevelClass implements BottomLevelInterface<MyType>

Мой вопрос: При реализацииTopLevelInterface Мне нужно не только передать аргумент типа для A, который будет BottomLevelClass, но и аргумент второго типа для B, который в вышеприведенном примере будет MyType.

Зачем мне нужно указывать B, что можно легко вывести, посмотрев на аргумент типа BottomLevelClass?

Например, при реализации TopLevelInterface мне нужно указать следующее:

class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel, MyType>

Вместо более короткой версии этого должно быть достаточно:

class TopLevelClass implements TopLevelInterface<ConcreteBottomLevel>

Зачем это нужно?Как я могу вывести аргумент второго типа, посмотрев на первый?Единственное решение, которое я придумал, - это использование infer в TypeScript 2.8+ с назначением по умолчанию.Это решение выглядит следующим образом:

interface TopLevelInterface<A extends BottomLevelInterface<B>, 
    B = A extends BottomLevelInterface<infer _B> ? _B : any>

Однако я не могу правильно применить это с трехуровневой иерархией классов.В следующем StackBlitz вы можете видеть, что я не могу получить правильное ограничение типа для свойства model из TopLevelClass.Это должно быть выведено для типа SomeType, но вместо этого выведено для never.На MiddleLevelClass он работает правильно.

https://stackblitz.com/edit/typescript-pcxnzo?file=infer-generics.ts

Может кто-нибудь объяснить проблему или лучший способ достичь желаемого результата?

1 Ответ

0 голосов
/ 20 октября 2018

Преднамеренная ошибка в MiddleLevelClass влияет на поведение TopLevelClass, поэтому для правильного теста мы должны основывать TopLevelClass на правильном MiddleLevelClass и использовать отдельный BadMiddleLevelClass, чтобы продемонстрировать ошибку там.

Ваша первая проблема заключается в том, что ваши условные типы имеют регистр "else" any, который будет скрывать ошибки.never имеет тенденцию быть лучше и обычно используется, хотя для полного решения потребуется уникальный invalid тип .

С этими изменениями кажется, что основная проблема заключается в том, что когда вынапишите TopLevelInterface<MiddleLevelClass>, а TypeScript попытается вычислить B = M extends MiddleLevelInterface<infer _B> ? _B : never, для _B нет выводов, поскольку параметр B на самом деле не используется MiddleLevelInterface или MiddleLevelClass.См. этот FAQ .Добавление фиктивного необязательного свойства, которое использует B, решает проблему.(Я полагаю, вы использовали B для чего-то в вашем реальном приложении, иначе вы бы не объявили B, но вы удалили использование в упрощенном примере?)

Новый код:

export class SomeType {
  x: string;
}

export interface BottomLevelInterface<T> {
  model : T;
}

export class BottomLevelClass implements BottomLevelInterface<SomeType> {
  model: SomeType;
}

export interface MiddleLevelInterface<B extends BottomLevelInterface<T>, 
    T = B extends BottomLevelInterface<infer _T> ? _T : never> {
  _dummy_B?: B;

  model: T;
} 

export class MiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
  _dummy_B?: BottomLevelClass;
  model: SomeType;
}

export class BadMiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
  // here we correctly see an error from TypeScript service, as 'string' cannot be applied to 'SomeType'
  model: string;
}

export interface TopLevelInterface <M extends MiddleLevelInterface<B, T>,
    B extends BottomLevelInterface<T> = M extends MiddleLevelInterface<infer _B> ? _B : never,
    T = B extends BottomLevelInterface<infer _T> ? _T : never> {

  model: T;
}

export class TopLevelClass implements TopLevelInterface<MiddleLevelClass> {
  // now there is an error here
  model: string;
}

Альтернативное решение исходной проблемы на основе предложения jcalz (спасибо!): Вместо использования нескольких параметров типа используйте псевдонимы вспомогательного типа для определения типа T из типа B и типа Bот типа M каждый раз, когда они вам нужны.Вот код:

export class SomeType {
  x: string;
}

export interface BottomLevelInterface<T> {
  model : T;
}

export class BottomLevelClass implements BottomLevelInterface<SomeType> {
  model: SomeType;
}

type TfromB<B extends BottomLevelInterface<any>> = B extends BottomLevelInterface<infer T> ? T : never;

export interface MiddleLevelInterface<B extends BottomLevelInterface<any>> {
  _dummy_B?: B;

  model: TfromB<B>;
} 

export class MiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
  _dummy_B?: BottomLevelClass;
  model: SomeType;
}

export class BadMiddleLevelClass implements MiddleLevelInterface<BottomLevelClass> {
  // here we correctly see an error from TypeScript service, as 'string' cannot be applied to 'SomeType'
  model: string;
}

type BfromM<M extends MiddleLevelInterface<any>> = M extends MiddleLevelInterface<infer B> ? B : never;

export interface TopLevelInterface <M extends MiddleLevelInterface<any>> {

  model: TfromB<BfromM<M>>;
}

export class TopLevelClass implements TopLevelInterface<MiddleLevelClass> {
  // now there is an error here
  model: string;
}

Я не удивлюсь, хотя, если зависимость от условных типов вместо явных параметров типа сделает некоторые операции, выходящие за рамки этого простого примера, более трудными.Вы можете попробовать и посмотреть.

...