【问题标题】:Of what type should a superclass be when using a subclass factory pattern in TypeScript?在 TypeScript 中使用子类工厂模式时,超类应该是什么类型?
【发布时间】:2018-10-24 18:54:27
【问题描述】:

我正在尝试在 TypeScript 3.x 中实现子类工厂模式。 考虑这个测试:

import { expect } from 'chai'

describe('subclass factory', () => {
  it('should work', () => {
    interface INameable {
      name?: string
    }

    const name = 'I am a Nameable!'

    function nameableSubclassOf<T> (superclass) {
      return class extends superclass implements INameable {
        name?: string = name
      }
    }

    class Foo {}

    const NameableFoo = nameableSubclassOf(Foo)
    const nameableFoo = new NameableFoo()

    expect(nameableFoo).to.be.instanceOf(Foo)
    expect(nameableFoo.name).to.be.ok
    expect(nameableFoo.name).to.equal(name)
  })
})

编译失败并显示以下消息:

TSError:⨯ 无法编译 TypeScript:

src/test/subclass-factory.ts(11,37):错误 TS7006:参数“超类”隐含地具有“任何”类型。

如何更改上述代码以成功编译并返回class,它是T 的子类,同时声明它实现了INameable

【问题讨论】:

    标签: typescript subclass factory


    【解决方案1】:

    我认为你的选择是要么告诉 Typescript superclassnewable,其语法是 briefly alluded to in the TypeScript handbook

    // "new (...args: any[]) => any" means the constructor takes any number of arguments 
    // and returns anything
    
    function nameableSubclassOf<C extends new (...args: any[]) => any>(superclass: C) {
      return class extends superclass implements INameable {
        name?: string = name
      }
    }
    

    这应该允许编译器为nameableSubclassOf 的返回值推断出一个可用但相当不透明的类型:

    const NameableFoo = nameableSubclassOf(Foo)
    // const NameableFoo: {
    //   new (...args: any[]): nameableSubclassOf<typeof Foo>.(Anonymous class);
    //   prototype: nameableSubclassOf<any>.(Anonymous class);
    // } & typeof Foo ?
    
    const nameableFoo = new NameableFoo();
    // const nameableFoo: nameableSubclassOf2<typeof Foo>.(Anonymous class) & Foo; ?
    
    const fooName = nameableFoo.name;
    // const fooName: string | undefined; ?
    

    ... 或者,如果您想要一个不依赖匿名类的更明确的类型,您可以使用泛型指定超类并使用conditional types 提取构造函数参数并从中返回类型:

    function nameableSubclassOf<C extends new (...args: any[]) => any>(
      superclass: C
    ): C extends new (...args: infer A) => infer T ? new (...args: A) => T & INameable : never;
    function nameableSubclassOf(
      superclass: new (...args: any[]) => any
    ): new (...args: any[]) => INameable {
      return class extends superclass implements INameable {
        name?: string = name
      }
    }
    

    请注意,我使用单个 overload 作为函数调用签名,这是调用者看到的。实现签名更宽松,因为编译器很难验证值是否可分配给条件类型......所以单签名重载是一种无需使用即可为函数调用者获得更多类型安全性的方法在实现中加载大量type assertions

    这更冗长,但是当你使用它时,你会得到更好的类型:

    const NameableFoo = nameableSubclassOf(Foo)
    // const NameableFoo: new () => Foo & INameable ?
    
    const nameableFoo = new NameableFoo()
    // const nameableFoo: Foo & INameable ?
    
    const fooName = nameableFoo.name
    // const fooName: string | undefined ?
    

    希望其中之一有所帮助。祝你好运!

    【讨论】:

    • 那是一些很棒的巫毒超魔法,实际上让我的测试通过了。我们可以把这变成一个学习的时刻,你有解释吗?让我的大脑崩溃的部分是function nameableSubclassOf&lt;C extends new (...args: any[]) =&gt; any&gt; (superclass: C): C extends new (...args: infer A) =&gt; infer T ? new (...args: A) =&gt; T &amp; INameable : never,它看起来是一个没有身体的function,除非我的大脑的TypeScript解析器搞砸了。
    • 我一直在使用 github.com/matthewadams/typescript-trait/pull/2 的这个子类工厂模式在 TypeScript 中支持特征。 @jcalz,你介意和我合作吗?具体来说,我想将解决方案再推广一层,以便实现的接口是可变的。
    • 关于 voodoo metamagic,我更新了答案以描述我如何使用单签名 function overload
    • 关于使实现的接口变量...除了超类之外,您将传递给函数什么?会是两个班吗?或者一个类和一个mixin(在这种情况下,你所做的事情与该页面上的 mixin 内容有很大不同)?
    【解决方案2】:

    这边?

    function nameableSubclassOf(superclass: {new(): any}) {
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-28
      • 1970-01-01
      • 2018-12-05
      相关资源
      最近更新 更多