【问题标题】:Return the subclass in the abstract class in TypeScript在 TypeScript 中返回抽象类中的子类
【发布时间】:2021-09-26 09:35:27
【问题描述】:

我已将我的问题简化为以下情况:

abstract class Foo {
  child: Foo
  foo(): Foo {
    return this.child
  }
}
class Bar extends Foo {
  bar = true
}
const foo = new Bar()
// Property 'bar' does not exist on type 'Foo'. ts(2339)
foo.foo().bar

这个错误是不言自明且不足为奇的,但我想要实现的是foo() 返回子类类型的某些内容。在本例中为Bar,这样我就可以使用它的属性bar。我们可以这样实现:

abstract class Foo<C extends Foo<C>> {
  child: C
  foo(): C {
    return this.child
  }
}
class Bar extends Foo<Bar> {
  bar = true
}
const foo = new Bar()
foo.foo().bar

是的,错误已解决!但是我也想在Foo 中使用this,就像我使用C 一样,即子类类型:

abstract class Foo<C extends Foo<C>> {
  foo(): C {
    // Type 'this' is not assignable to type 'C'.
    // 'this' is assignable to the constraint of type 'C',
    // but 'C' could be instantiated with a different subtype of constraint 'Foo<C>'. ts(2322)
    return this
  }
}
class Bar extends Foo<Bar> {
  bar = true
}
const foo = new Bar()
foo.foo().bar

我们不能,因为 TypeScript 不知道作为类型参数传递的子类类型实际上与实际的子类类型相同。

有没有办法解决这个问题,这样我就不会出现这个错误?

我可以通过转换 this 来解决它,如下所示:this as unknown as C,让 TypeScript 知道相信我们它实际上是相同的,但是因为我有很多代码在 C 的上下文中使用 this意料之中,这是我宁愿不做的事情。

编辑:我开始使用一些包含共享功能的函数,而不是使用基类:

interface Foo {
  child: Foo
}
function fooFn<C extends Foo>(parent: C, child: C): C {
  return child
}
class Bar implements Foo {
  child: Bar
  bar = true
  foo() {
    return fooFn(this, this.child)
  }
}
const foo = new Bar()
foo.foo().bar

这对我的用例很有效,但现在它变成了 20 个这样的函数,我觉得让这些方法直接调用函数的样板可以被基类替换,但后来我遇到了这个问题。

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    你正在与 typescript 的类型检查作斗争。

    总是有一些老套的方法。任意类型可以用于返回C:

    return this as any
    

    Typescript 正在做预期的事情,并保护您免受自己的伤害。 将对象强制放入扩展类会导致数据丢失,因此您需要向编译器展示您知道这一点。

    【讨论】:

    • 所以你是说我有正确的方法来实现我想要实现的目标,这种情况是一个公平的例子,必须强制转换为 any,因为这在 TypeScript 中并没有很好地表达任何其他方式?
    • 我之前遇到过类似的情况,我别无选择,我感觉这也是一种。您可能可以通过其他方式解决此问题(老实说,我不知道,但通常总是如此)。我认为问题是:“以其他方式执行此操作所需的代码量是否值得,或者它只会变得混乱并降低可读性?”如果您的代码完成了它应该做的事情,我会这样做。但我绝不是打字稿专家,我只是遇到过类似的情况。
    • 我怀疑真的有更好的方法,因为这是多种语言中的重复模式(请参阅我的回答),但你是对的,它可能已经足够好了,因为其他解决方案可能会导致更多样板。
    【解决方案2】:

    我更深入地研究了这种模式,因为我看到一些模糊的提及它也在 C++ 和 C# 中使用,显然它被称为Curiously Recurring Template Pattern (CRTP)。他们也只是在他们的例子中投了this,所以这似乎是一种合法的方式。由于我对 C# 更加熟悉,因此我寻找了它的一个示例,并找到了 a post 这个示例:

    public abstract class AlgorithmBase<TDerived>
        where TDerived : AlgorithmBase<TDerived>
    {
        public TDerived Run()
        {
            this.Step1();
            this.Step2();
            // ...
            this.StepN();
            this.FinalStep();
            
            return (TDerived)this;
        }
        
        protected abstract void Step1();
        protected abstract void Step2();
        // ...
        protected abstract void StepN();
        protected abstract void FinalStep();
    }
    

    翻译成 TypeScript 会变成:

    abstract class AlgorithmBase<TDerived extends AlgorithmBase<TDerived>> {
      run(): TDerived {
        this.step1();
        this.step2();
        // ...
        this.stepN();
        this.finalStep();
        
        return this as any; // or: as unknown as TDerived,
        // but that is unnecessary as we already gave an explicit return type
      }
    
      protected abstract step1();
      protected abstract step2();
      // ...
      protected abstract stepN();
      protected abstract finalStep();
    }
    

    如果有更好的方法来解决这个问题,我本来希望他们有维基百科页面和关于他们的帖子,所以我认为这已经是最好的了。

    【讨论】:

    • 有趣,谢谢分享。看到你做对了总是很高兴。 XD
    猜你喜欢
    • 2010-12-13
    • 1970-01-01
    • 2021-05-12
    • 2016-12-28
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 2020-07-03
    相关资源
    最近更新 更多