Получение n-го типа параметра из ConstructorParameters - PullRequest
1 голос
/ 02 мая 2019

Я пытаюсь использовать тип ConstructorParameters, чтобы получить первый тип аргумента конструктора класса в качестве расширения для интерфейса (вероятно, лучше показать код):

// base.ts ---------------------------------------------------------------------
interface IConfig {
  someProperty: string;
}

export class Base {
  constructor(config: IConfig) {
  }
}

// derived.ts ------------------------------------------------------------------
interface IConfig extends ConstructorParameters<typeof Base> {
                                                         // ^ tried [0] here but 
                                                         // that doesn't work
}

export class Derived extends Base {
  constructor(config: IConfig) {
    super(config); // this doesn't work because ConstructorParameters returns
                   // a type tuple which is then placed on IConfig for this
                   // class.  I want to pick the type of the first argument
                   // and I can't figure out how to select just the first
                   // arguments type (i.e. [0] but that doesn't work).
  }
}

Кто-нибудь имеет представление о том, как заставить эту работу работать, или знаете что-то, что я не использую для типа ConstructorParameters?

Ответы [ 2 ]

1 голос
/ 02 мая 2019

Просто разобрался (хотя и с другими предложениями):

// derived.ts ------------------------------------------------------------------
type BaseConfigType = ConstructorParameters<typeof Base>[0];
interface IConfig extends BaseConfigType {

}

export class Derived extends Base {
  constructor(config: IConfig) {
    super(config); // this now works :)
  }
}
0 голосов
/ 02 мая 2019

Я не большой поклонник наследования, чтобы обеспечить расширяемость (в смысле принципа Open / Closed ) .Тем не менее, в этой ситуации я бы предпочел спроектировать базовый класс так, чтобы он был явно расширяемым и для чего, здесь, в его входном параметре конфигурации, используя ограничение общего типа и экспортируя базовый тип конфигурации.

// base.ts ---------------------------------------------------------------------
export interface IBaseConfig {
  someProperty: string;
}

export class Base<TConfig extends IBaseConfig = IBaseConfig> {
  constructor(config: TConfig) {
  }
}

// derived.ts ------------------------------------------------------------------
interface IDerivedConfig extends IBaseConfig {
  otherProperty: string;
}

export class Derived extends Base<IDerivedConfig> {
  constructor(config: IDerivedConfig) {
    super(config);
  }
}

Редактировать

Когда «базовый» класс не в нашей руке, требуется дополнительная осторожность:

  1. Если «базовый» класс не являетсяпредназначен для расширения наследованием, не наследуйте от этого класса, используйте композицию с forwarding .Таким образом, мы защищаем себя от трудно обнаруживаемых ошибок, таких как Хрупкий базовый класс .Больше информации: см. Состав по наследованию .
  2. Защитите клиента нашего класса от изменений в "базовом" классе.Освойте все открытые типы и скройте другие типы, например, обернув тип из base.ts.

Это один из вариантов:

// base.ts ---------------------------------------------------------------------
interface IConfig {
  someProperty: string;
}

export class Base {
  constructor(config: IConfig) { }
  compute(arg: any): string { return 'any-string'; }
  do(): void {}
}

// derived.ts ------------------------------------------------------------------
export interface IDerivedConfig { /* TBD */ }

export class Derived {
  static create(config: IDerivedConfig): Derived {
    const baseConfig = {} as any /* TODO: replace `{} as any` with the custom mapping from `config` to a base config object. */;
    const base = new Base(baseConfig);
    return new Derived(base);
  }

  private constructor(
    private readonly base: Base,
  ) { }

  compute(arg: any) {
    return this.base.compute(arg);
  }

  do() {
    this.base.do();
  }
}

Примечания:

  • IDerivedConfig не расширяет "IConfig" (который мы получаем с вашим наконечником: type BaseConfigType = ConstructorParameters<typeof Base>[0]) , чтобы быть полностью в наших руках и предотвратить изменение вбазовый конфиг для распространения на все строительные площадки наших клиентов.
  • Derived класс implements Base (вместо extends Base).Тем не менее, нет необходимости кодировать его, но сначала нужно быть уверенным в совместимости типов.Тогда удаление будет безопаснее.Если базовый класс изменяется, мы можем распространить это изменение или просто закодировать наш класс как Adapter .
  • Derived класс имеет статический метод фабрики для его создания, функцию, которая вызываетзакрытый конструктор, который определяет встроенный частный доступный только для чтения «базовый» объект.
  • В методе Derived.create() baseConfig вводится неявно с помощью логического вывода.Код безопасен, потому что компилятор не позволяет нам передавать несовместимые аргументы в конструктор Base.
...