有没有办法把混入类作为类来使用,而不是把它们作为值来使用?
我在按照TypeScript的 混入(mixin)文档 的示例来做,这个示例向我承诺:
一切就绪后,你就可以创建一个表示应用了混入的基类的类:
```typescript class Sprite {...}
function Scale
{}>(Base: TBase){...} // Compose a new class from the Sprite class, // with the Mixin Scale applier: const EightBitSprite = Scale(Sprite); ```
然而,我发现 EightBitSprite 实际上是一个值,而不是一个类,即:
const mapOfSprites = new Map<string, Sprite>(); //Works
//TS2749: EightBitSprite refers to a value, but is being used as a type here. Did you mean typeof EightBitSprite?
const mapOfEightBitSprites = new Map<string, EightBitSprite>();
这点挺烦人的,因为:
- 在我的代码中无论何处使用一个类的类型时,我都需要记住它是一个真正的类还是一个混入类。
- 如果把一个类的声明改成混入类,那么就需要更新所有使用其类型的地方。比如把
class ScalableTextBox {...pre-mixin-impl...}→const ScalableTextBox = Scale(TextBox)时,我需要把所有function(): ScalableTextBox {...}改成function(): typeof ScalableTextBox {...},以此类推。
迄今为止我发现的唯一变通办法,是定义一个第三个类,让它扩展对基类进行混入后的结果,即:
//Works
class RealEightBitSprite extends EightBitSprite {}
const mapOfRealEightBitSprites = new Map<string, RealEightBitSprite>();
这样还可以。它解决了问题#2(例如 class ScalableTextBox {...} → class ScalableTextBox extends Scale(OldScalableTextBox){})。然而:
- 对我来说,这种做法显得笨拙/不优雅。也许是因为它导致了相当复杂的类层次结构:
RealEightBitSprite extends EightBitSprite extends Scaling extends Sprite - 更重要的是,问题#1变得更糟。我现在有两个不同的“类”,
RealEightBitSprite和EightBitSprite在功能上完全相同,容易混淆。它们之间唯一显著的差别是RealEightBitSprite既是一个类型也是一个值,而EightBitSprite只是一个值。
有没有办法让 EightBitSprite 同时既是一个值又是一个类型?关于 Declaration Merging 的文档暗示没有这样的方法,并把我送回到Mixins,因此我怀疑确实没有解决方案。
解决方案
我会把“真正的类”理解为一个 class 声明。
在TypeScript中,class 声明会创建一个值(就像在JavaScript中一样),但它也会隐式创建一个同名的类型。这个类型表示该类的实例。
这也是像下面这样的代码之所以合法的原因:这个 的写法是合法的:
class Foo { isFoo = true }
const foo: Foo = new Foo()
// ^ type ^ value
上述写法大致等价于 这个:
const Foo = class { isFoo = true }
type Foo = InstanceType<typeof Foo>
const foo: Foo = new Foo()
// ^ type ^ value
掌握了这些知识后,你可能会发现让 EightBitSprite 看起来像一个“真正的类”的一种方式,是通过显式声明它的实例类型:
const EightBitSprite = Scale(Sprite)
type EightBitSprite = InstanceType<typeof EightBitSprite>
const mapOfEightBitSprites = new Map<string, EightBitSprite>()
// ^ this is now legal
另一种方法是使用 class 声明来定义 EightBitSprite.这里不需要第三个 RealEightBitSprite 类:
class EightBitSprite extends Scale(Sprite) {}
不管哪种方式,你现在都会得到一个名为 EightBitSprite 的类型,它同时包含来自基类 Sprite 的属性和混入的属性。