网站搜索

混入 - 将 TypeScript 混入应用到通用基类以获得通用派生类


在 TypeScript 中,我定义了一个带类型参数 T 的接口。它用于某种类型为 T 的值的容器,可以通过多种方式实现。接口上的某些方法需要针对每种实现进行专门化。但其他方法可以给出一个通用实现(针对 T),其结果可以仅通过调用核心方法来获得。因此,我想我可以通过使用 混入 来实现,将一个只实现核心方法的类扩展为实现所有方法的类。

但是我还没有能够找到有效的 TypeScript 语法,将混入应用于泛型基类以生成泛型派生类。

让我给一个简化的例子。我把我的接口分成核心部分(Base<T>)和派生部分(Derived<T>)。

interface Base<T> {
    foo(x: number): T[];
}

interface Derived<T> extends Base<T> {
    doubleFoo(x: number): T[];
}

我可以在基础接口上实现一个类,比如这样:

class BaseImpl<T> implements Base<T> {
    constructor(public t: T) {}

    foo(x: number) {
        return new Array(x).fill(this.t);
    }
}

现在我想写一个 mix-in 函数,给定 BaseImpl,它将返回一个实现 Derived 的类。基类和派生实现类仍然应该有 T 作为类型参数。

我最初尝试了以下方法:

type Constructor<I> = new (...args: any[]) => I;

const MixInDoubleFoo = <T, C extends Constructor<Base<T>>> (BaseClass: C) => {
    return class extends BaseClass implements Derived<T> {

        doubleFoo(x: number) {
            return this.foo(x * 2);
        }
    }
}

const DerivedImpl = MixInDoubleFoo(BaseImpl);

这可以编译通过,没有错误。但似乎生成的类失去了一些类型信息。当我尝试以下操作时,出现了错误。

class MyDerivedFromMixin<T> extends DerivedImpl<T> {

    tripleFoo(x: number): T[] {
        // error: Type 'unknown[]' is not assignable to type 'T[]'.
        // error:   Type 'unknown' is not assignable to type 'T'.
        // error:     'T' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.
        return this.foo(3 * x);
    }
}

为了对比,如果我尝试以完全相同的方式扩展基实现类,它会编译而不会出错。

class MyDerivedFromBaseImpl<T> extends BaseImpl<T> {

    tripleFoo(x: number): T[] {
        return this.foo(3 * x);
    }
}

在我看来,TypeScript 的类型推断未能将 T 视为从混入返回的函数的类型参数,而是将其替换为 unknown。

我阅读了这个问题:为什么这个使用泛型的 TypeScript mixin 编译失败?那个问题的情况不同——提问者试图在 mix-in 函数中添加一个类型参数,而我则试图从基类中提升一个类型参数。

尽管如此,它确实告诉我,当只提供部分而不是全部模板参数时,TypeScript 不会推断类型。我尝试了那里建议的两种解决方案(对 mixin 函数进行柯里化,提供一个额外的虚拟参数以简化类型推断)。但尽管我尝试了许多不同的语法,没有一个是编译器接受的。

我想最终得到的是一个混入类,它将允许我写成 DerivedImpl=MixIn(BaseImpl),这样我就可以接着做:

  • 创建类的实例:const instance=new DerivedImpl<T>(...)
  • 将其扩展为一个以 T 为参数化的类:class Extension<T> extends DerivedImpl<T> { ... }

有办法做到这一点吗?我开始觉得我应该放弃尝试使用 mix-in,而只是把‘派生’方法做成针对 T 的模板函数,这些函数接受基类的一个实例。这显然可行,只是看起来有些丑陋。


包含以上所有代码的 Typescript 游乐场:playground

我正在使用 TypeScript 3.9。