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

Overloading class properties based on constructor arguments in typescript

在我们的代码库中,我们相当广泛地使用导航器和构建器模式来抽象化组装分层对象。其核心是 Navigator class,我们用它来遍历不同的 classes。我目前正在尝试将其迁移到打字稿,但正在努力打字以利用打字稿的力量。

我认为我的问题的核心是我不能使用 this 作为 class 上泛型的默认值,例如class Something<T = this>,或者我无法重载 class 以某种方式有条件地设置 class 属性的类型。您能否提供有关我如何在下面键入 Navigator(和构建器 classes)的任何见解?

// 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

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

我认为你最好的选择是为每个 class 创建一个特殊的变体,它像你的选项一样工作,没有构造函数参数。因此有一个特殊的 SelfNavigatorSelfBuilder1 等扩展它们相应的 classes,并且本身没有泛型类型。

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),因此使用了丑陋的转换。有不同的方法可以避免这种情况,使代码更好,例如将所有内容转换为接口系统和抽象 classes,以便字段 back 不在 superclass 中,而是在超级接口中。或者,如果未设置,则将其设为 getter 和 return this,尽管这仍需要一些转换。