Перегрузка свойств класса на основе аргументов конструктора в машинописи - PullRequest
2 голосов
/ 04 июля 2019

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

Я думаю, что суть моей проблемы в том, что я не могу использовать this в качестве значения по умолчанию для универсального объекта в классе, например, class Something<T = this>, или что я не могу перегрузить класс, чтобы каким-либо образом обусловить егоустановить типы свойств класса.Можете ли вы рассказать, как я могу набрать Navigator (и классы строителей) ниже?

// I guess what I'd like to do is
// class Navigator<BackT = this> but that's not allowed
class Navigator<BackT> {
  // It's this 'back' type I'll like to define more specifically
  // i.e. if back is passed to the constructor then it should be 'BackT'
  //      if back is not passed to the constructor or is undefined, it 
  //      should be 'this'
  back: BackT | this; 

  constructor(back?: BackT)  {
    this.back = back || this;
  }
}

class Builder1<BackT> extends Navigator<BackT> {
  builder1DoSomething() {
    // Do some work here
    return this;
  }
}

class Builder2<BackT> extends Navigator<BackT> {
  withBuilder1() {
    return new Builder1(this);

    // Also tried the following, but I got the same error:
    // return new Builder1<this>(this);
  }

  builder2DoSomething() {
    // Do some work here
    return this;
  }
}

// This is fine
new Builder1().builder1DoSomething().builder1DoSomething();

new Builder2()
  .withBuilder1()
  .builder1DoSomething()
  // I get an error here becuase my types are not specific enough to
  // let the complier know 'back' has taken me out of 'Builder1' and
  // back to 'Builder2'
  .back.builder2DoSomething();

ссылка на игровую площадку

Ответы [ 2 ]

2 голосов
/ 04 июля 2019

Вы можете использовать условный тип в поле back, чтобы ввести его как this, если аргумент типа не был предоставлен классу.Мы будем использовать тип void в качестве значения по умолчанию, чтобы сигнализировать об отсутствии аргумента типа:

class MyNavigator<BackT = void> {
  back: BackT extends void ? this : BackT; // Conditional type 

  constructor(back?: BackT)  {
    this.back = (back || this) as any;
  }
}

class Builder1<BackT = void> extends MyNavigator<BackT> {
  builder1DoSomething() {
    return this;
  }
}

class Builder2<BackT = void> extends MyNavigator<BackT> {
  withBuilder1() {
    return new Builder1(this);
  }
  builder2DoSomething() {
    return this;
  }
}

new Builder2()
  .withBuilder1()
  .builder1DoSomething()
  // ok now
  .back.builder2DoSomething();
0 голосов
/ 04 июля 2019

Я думаю, что ваш лучший вариант - создать специальный вариант для каждого класса, который работает как ваш вариант без параметра конструктора.Поэтому имейте специальные SelfNavigator, SelfBuilder1 и т. Д., Расширяющие их соответствующие классы и не имеющие общих типов.

class MyNavigator<BackT> {
  back: BackT;

  constructor(back: BackT) {
    this.back = back;
  }
}

class SelfMyNavigator extends MyNavigator<SelfMyNavigator> {}

class Builder1<BackT> extends MyNavigator<BackT> {
  builder1DoSomething() {
    // Do some work here
    return this;
  }
}

class SelfBuilder1 extends Builder1<SelfBuilder1> {
  constructor() {
    super((null as unknown) as SelfBuilder1);
    this.back = this;
  }
}

class Builder2<BackT> extends MyNavigator<BackT> {
  withBuilder1() {
    return new Builder1(this);
  }
  builder2DoSomething() {
    // Do some work here
    return this;
  }
}

class SelfBuilder2 extends Builder2<SelfBuilder2> {
  constructor() {
    super((null as unknown) as SelfBuilder2);
    this.back = this;
  }
}

// This is fine
new SelfBuilder1().builder1DoSomething().builder1DoSomething();

new SelfBuilder2()
  .withBuilder1()
  .builder1DoSomething()
  .back.builder2DoSomething();

игровая ссылка

Обратите внимание, чтоВы не можете позвонить super(this), поэтому используются уродливые приведения.Есть разные способы избежать этого, чтобы сделать код лучше, например, преобразовать все в систему интерфейсов и абстрактных классов, чтобы поле back находилось не в суперклассе, а скорее в суперинтерфейсе.Или сделайте его геттером и верните this, если он не установлен, хотя для этого все равно потребуются некоторые приведения.

...