根据打字稿中的构造函数参数重载类属性

时间:2019-07-04 12:22:08

标签: typescript typescript-typings typescript-generics

在我们的代码库中,我们非常广泛地使用导航器和构建器模式来抽象化组装的分层对象。其核心是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();

playground link

2 个答案:

答案 0 :(得分:2)

如果没有为类提供类型参数,则可以在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();

答案 1 :(得分:0)

我认为最好的选择是为每个类创建一个特殊的变体,该变体的工作方式与没有构造函数参数的情况类似。因此,有一个特殊的SelfNavigatorSelfBuilder1等扩展了它们对应的类,并且本身没有泛型。

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();

playground link

请注意,您无法致电super(this),因此使用了丑陋的演员表。有多种方法可以避免这种情况,例如使代码更好。将所有内容都转换为接口和抽象类的系统,以便字段back不在超类中,而是在超接口中。或者将其设为吸气剂,如果未设置,则返回this,尽管这仍然需要一些强制转换。

相关问题