【问题标题】:How to alias complex type constructor in typescript?如何在打字稿中为复杂类型构造函数起别名?
【发布时间】:2016-12-05 20:09:37
【问题描述】:

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

 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) 不相称。

【问题讨论】:

标签: oop typescript


【解决方案1】:

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

解决方案 1:类型断言

假设超类如下所示:

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

要创建专门的类型别名,您可以这样做:

type TransformA = MapTransform<number>;

从超类MapTransform来看,MapTransform&lt;A&gt;MapTransform&lt;B&gt;等没有区别(这就是泛型的意义所在),所以我们可以放心地将类MapTransform的构造函数赋值给一个常数TransformA。调用new TransformA()在运行时与调用new MapTransform&lt;number&gt;() 相同:

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&lt;number&gt;

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

  • new TransformA(123) instanceof TransformA
  • new TransformA(123) instanceof MapTransform
  • new MapTransform&lt;number&gt;(123) instanceof TransformA
  • new MapTransform&lt;number&gt;(123) instanceof MapTransform

这是TypeScript Playground 中的示例。

解决方案 2:子类化

再次假设超类如下所示:

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

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

class TransformA extends MapTransform<number> { }

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

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

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

...虽然这些将评估false:

  • new MapTransform&lt;number&gt;(123) instanceof TransformA
  • new MapTransform&lt;number&gt;(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);
    }
}

更多关于类型

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


编辑

您写道,将这些解决方案应用于需要函数作为参数的类时遇到了问题,所以这里有另一个 example in the TypeScript playground

【讨论】:

  • 我已经用 MapTransform 构造函数的构造函数更新了我的问题。我仍然很难将您的解决方案应用于需要函数的构造函数。
  • @k0pernikus 我添加了另一个示例,希望对您有所帮助。
  • 这么棒的答案。我无话可说。
  • 第一个解决方案的操场似乎不再起作用了。我也收到了一个 eslint 警告TransformA is already defined。这是预期的吗?
猜你喜欢
  • 2017-01-29
  • 2019-07-05
  • 2019-04-13
  • 1970-01-01
  • 2017-03-03
  • 2020-10-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多