如何在打字稿中别名复杂类型构造函数?



我正在使用自定义库,对于某些类型,我必须编写:

import * as pipeline from 'custom-pipeline';
import {MapTransform} from 'custom-pipeline';
export const createTransform = (appConfig: IAppConfig):
MapTransform<ISomeData, IFoo> |
MapTransform<ISomeData, IBar> => {
switch(appConfig.createType) {
case 'foo':
return new pipeline.MapTransform<ISomeData, IFoo>((data: ISomeData) => {...});
case 'bar':
return new pipeline.MapTransform<ISomeData, IBar>((data: ISomeData) => {...});
}
}

尤其是冗长的构造函数让我很恼火。我可以为类型别名:

type FooTransform = MapTransform<ISomeData, IFoo>;
type BarTransform = MapTransform<ISomeData, IBar>;

然而我不能做到:

new FooTransform((data: ISomeData) => {...});
new BarTransform((data: ISomeData) => {...});

抛出如下错误:

error TS2304: Cannot find name 'FooTransform'.

我想这是因为我只有一个类型而不是一个类?但是,我如何以上述方式为构造函数指定别名new FooTransform

MapTransform的定义如下所示:

export declare class MapTransform<A, B> extends BaseTransform<A, B> {
constructor(private mapFunc: (val: A) => B);
}

我可以将构造函数简化为:

fooMapFunction = (data: ISomeData): IFoo => {...};
new MapTransform<ISomeData, IFoo>(mapFunction);

虽然它与new FooTransform(fooMapFunction)不相上下.

您的假设是正确的,类型声明仅在编译时,因此您无法对它们执行任何操作(包括使用new进行实例化)。

解决方案 1:类型断言

假设超类看起来像这样:

class MapTransform<T1> { 
constructor(public readonly data: T1) { /* ... */ }
}

要创建专用类型别名,您可以执行以下操作:

type TransformA = MapTransform<number>;

从超类MapTransform的角度来看,MapTransform<A>MapTransform<B>等之间没有区别(这是泛型的意义所在),因此我们可以安全地将类MapTransform的构造函数分配给常量TransformA。在运行时,调用new TransformA()与调用new MapTransform<number>()相同:

const TransformA = <{ new (data: number): TransformA; }>MapTransform;

注意到类型断言了吗?这告诉 TypeScript 编译器将分配的值MapTransform视为一个对象,该对象在使用new实例化时构造类型TransformA的对象。我们现在可以写:

const a = new TransformA(123);
a.data.toExponential(); // works

顺便说一句,TypeScript 编译器实际看到的是这个......

const TransformA = <{ new (data: number): MapTransform<number>; }>MapTransform;

。因为键入TransformA ≡ MapTransform<number>.

请注意,所有这些都将评估true

  • new TransformA(123) instanceof TransformA
  • new TransformA(123) instanceof MapTransform
  • new MapTransform<number>(123) instanceof TransformA
  • new MapTransform<number>(123) instanceof MapTransform

下面是 TypeScript Playground 中的示例。

解决方案 2:子类化

同样,让我们假设超类看起来像这样:

class MapTransform<T1> { 
constructor(public readonly data: T1) { /* ... */ }
}

使用子类化,解决方案很简单:扩展超类并在扩展子句中传递所需的类型参数。

class TransformA extends MapTransform<number> { }

瞧,您现在有一个在运行时工作的构造函数以及一个在编译时工作的类型。

与第一个解决方案不同,以下 2 个表达式将计算true...

  • new TransformA(123) instanceof TransformA
  • new TransformA(123) instanceof MapTransform

。虽然这些将评估false

  • new MapTransform<number>(123) instanceof TransformA
  • new MapTransform<number>(123) instanceof MapTransform

解决方案 2.1:匿名子类化

如果只需要构造函数别名而不需要类型别名,这可能会派上用场:

const TransformA = class extends MapTransform<number> { };

这称为类表达式,可以像其他表达式一样使用,例如:

class App {
private static TransformA = class extends MapTransform<number> { };
public doStuff() {
return new App.TransformA(123);
}
}

有关类型的更多信息

如果您有兴趣,这里有一些关于该主题的链接:

  • TypeScript 规范中的命名类型
  • 类型断言
  • TypeScript 手册:接口,特别是"类的静态端和实例端之间的区别"部分
  • 类表达式

编辑

您写道,在将这些解决方案应用于需要函数作为参数的类时遇到了问题,因此下面是 TypeScript 操场中的另一个示例。

最新更新