根据打字稿中的构造函数参数重载 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();
如果没有向 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 创建一个特殊的变体,它像你的选项一样工作,没有构造函数参数。因此有一个特殊的 SelfNavigator
、SelfBuilder1
等扩展它们相应的 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();
请注意,您不能调用 super(this)
,因此使用了丑陋的转换。有不同的方法可以避免这种情况,使代码更好,例如将所有内容转换为接口系统和抽象 classes,以便字段 back
不在 superclass 中,而是在超级接口中。或者,如果未设置,则将其设为 getter 和 return this
,尽管这仍需要一些转换。
在我们的代码库中,我们相当广泛地使用导航器和构建器模式来抽象化组装分层对象。其核心是 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();
如果没有向 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 创建一个特殊的变体,它像你的选项一样工作,没有构造函数参数。因此有一个特殊的 SelfNavigator
、SelfBuilder1
等扩展它们相应的 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();
请注意,您不能调用 super(this)
,因此使用了丑陋的转换。有不同的方法可以避免这种情况,使代码更好,例如将所有内容转换为接口系统和抽象 classes,以便字段 back
不在 superclass 中,而是在超级接口中。或者,如果未设置,则将其设为 getter 和 return this
,尽管这仍需要一些转换。