有没有办法把混入类作为类来使用,而不是把它们作为值来使用?

前端开发 2026-07-09

我在按照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>();

这点挺烦人的,因为:

  1. 在我的代码中无论何处使用一个类的类型时,我都需要记住它是一个真正的类还是一个混入类。
  2. 如果把一个类的声明改成混入类,那么就需要更新所有使用其类型的地方。比如把 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变得更糟。我现在有两个不同的“类”,RealEightBitSpriteEightBitSprite 在功能上完全相同,容易混淆。它们之间唯一显著的差别是 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 的属性和混入的属性。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章