从ConstructorParameters获取第n个参数类型

时间:2019-05-02 08:30:30

标签: typescript generics typescript-generics

我正在尝试使用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 个答案:

答案 0 :(得分:1)

只需弄清楚(虽然可以接受其他建议):

// 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 :)
  }
}

答案 1 :(得分:0)

我不是继承的忠实拥护者,不愿意提供扩展性(以Open/Closed Principle的方式)。尽管如此,在这种情况下,我还是希望设计基类,以便它可以显式地进行扩展,并在其配置输入参数上使用通用类型约束并导出基配置类型。

// 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一起使用。这样,我们保护自己免受难以发现的Fragile Base Class之类的错误。更多信息:请参见Composition over Inheritance
  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],以便完全掌握并防止更改在基本配置中传播到客户端中的所有建筑工地。
  • Derivedimplements Base(而不是extends Base)。不过,仍然不需要对其进行编码,但是在开始时要确保类型兼容性。然后将其卸下更安全。如果基类发生更改,我们可以选择传播更改,也可以仅将我们的类编码为Adapter
  • Derived类有一个静态工厂方法来创建它,该函数调用定义内联的私有构造函数的私有只读“基”对象。
  • Derived.create()方法中,baseConfig是通过推断隐式键入的。该代码是安全的,因为编译器阻止我们将不兼容的参数传递给Base构造函数。