如何在 TypeScript 中键入带有类的数组?

Posted

技术标签:

【中文标题】如何在 TypeScript 中键入带有类的数组?【英文标题】:How to type an array with classes in TypeScript? 【发布时间】:2016-10-05 18:17:54 【问题描述】:

我有一个应用程序,它通过运行它的方法 .init(params) 来初始化,如下所示:

app.init([TopBar, StatusBar, MainArea]);

其中TopBarStatusBarMainArea 是类,而不是类的实例。这些类中的每一个都实现了相同的接口IComponent

我想在 .init(params) 方法中从传递的类中实例化对象,如下所示:

init(params: IComponent[]): void 
    params.map(function (component) 
        let comp = new component();
        this.components[comp.constructor.name] = comp;
    , this);

问题在于,由于这些不是实例,TypeScript 不知道它们的类型并引发错误:

错误 TS2345:类型参数 '(typeof TopBar | typeof StatusBar | typeof MainArea)[]' 不可分配给类型参数 'IComponent[]'。

如何修复代码,以便将实现某个接口的类数组传递给方法?

【问题讨论】:

为什么不使用工厂函数而不是尝试在类上使用 new? 【参考方案1】:

我发现了两种不同的方法可以为这种情况创建类型:

// Interface style:
export default interface IConstructor<T> extends Function 
  new (...args: any[]): T;


// Union Type style:
export type ConstructorUnion<T> = new(...args : any[]) => T;

这就是IConstructor 类型的外观:

interface IComponent  
class TopBar implements IComponent  
class StatusBar implements IComponent  
class MainArea  

class App 
  public components:  [key: string]: IComponent  = ;

  public init(params: IConstructor<IComponent>[]): void 
    params.forEach((Component: IConstructor<IComponent>) => 
      const comp: IComponent = new Component();

      this.components[comp.constructor.name] = comp;
    );
  


const app = new App();

app.init([TopBar, StatusBar, MainArea]);

console.clear();
console.log(app);

代码如下: https://stackblitz.com/edit/how-to-type-an-array-with-classes-in-typescript?file=index.ts

【讨论】:

【参考方案2】:

尽管这是一个老问题:这就是你的做法:

interface IComponent  something(): void; 
class TopBar implements IComponent  something()  console.log('in TopBar'); 
class StatusBar implements IComponent  something()  console.log('in StatusBar'); 
class MainArea implements IComponent  something()  console.log('in MainArea'); 

interface ComponentClass 
    new(): IComponent;


const components:  [name: string]: IComponent  = ;

function init(params: ComponentClass[]) 
    params.map((component) => 
        let comp = new component();
        components[component.name] = comp;
    );


init([TopBar, StatusBar, MainArea]);

for (const c in components) 
    console.log('Component: ' + c);
    components[c].something();

【讨论】:

【参考方案3】:

Typescript 支持类类型泛型 (TypeScript Docs)。他们的例子是:

function create<T>(c: new(): T; ): T 
    return new c();

上面写着“将一个类传递到我的create 方法中,该类在构造时将返回我想要的类型 T”。此签名将阻止您尝试传入任何非 T 类型的类类型。

这与我们想要的很接近,我们只需要对其进行调整,使其成为您IComponent 的项目和项目的数组。

public init(components: new(): IComponent;[]): void 
    // at this point our `components` variable is a collection of
    // classes that implement IComponent

    // for example, we can just new up the first one;
    var firstComponent = new components[0]();
, this);

有了方法签名,我们现在可以像这样使用它了

app.init([TopBar, StatusBar, MainArea]);

我们传入实现IComponent的类型数组的地方

【讨论】:

【参考方案4】:

有一个working typescript playground(运行它以获得结果警报)

我们需要创建一个自定义的type InterfaceComponent。这将是init() 方法的数组

interface IComponent  
class TopBar    implements IComponent  
class StatusBar implements IComponent  
class MainArea  implements IComponent  

// this is a type we want to be passed into INIT as an array
type InterfaceComponent = (typeof TopBar | typeof StatusBar | typeof MainArea);

class MyClass 

  components: [key:string] : IComponent  = ;

  init(params: (InterfaceComponent)[]): void 
    params.map((component) => 
        let comp = new component();
        this.components[comp.constructor["name"]] = comp;
    , this);
  


let x = new MyClass();
x.init([TopBar, StatusBar, MainArea])

alert(JSON.stringify(x.components))

查看here

【讨论】:

这是最优雅的解决方案,谢谢!不过,这意味着,如果我有很多组件,比如 100+,我是否必须将它们全部添加到 InterfaceComponent 类型中? 好吧,如果不深入研究就很难说......遵循上述方法......我们需要扩展它......这对于 100+ 来说是不行的。一些工厂方法,使用传递的未知类型(不是那么类型安全)会更合适,我会说......var instance = Object.create(objectType.prototype); instance.constructor.apply(instance, constructorParameters); 继承是错误的——InterfaceComponent 需要知道所有实现它的类,这肯定不是一件好事吗?【参考方案5】:

也许您可以将comp 的类型指定为InterfaceComponent

var comp: InterfaceComponent = new component();
this.components[comp.constructor.name] = comp;

【讨论】:

我对 TypeScript 做的最后一件事就是将所有内容的类型分配给 any。 如何分配InterfaceComponent【参考方案6】:

改用工厂方法。声明有点笨拙,但想法有效:

interface InterfaceComponent 
    name: string;


class TopBar implements InterfaceComponent 
    name: string;


class StatusBar implements InterfaceComponent 
    name: string;


class MainArea implements InterfaceComponent 
    name: string;


interface InterfaceComponentFactory 
    create: () => InterfaceComponent;


function init(params: InterfaceComponentFactory[]): void 
    params.map(function (component) 
        let comp = component.create();
        this.components[comp.name] = comp;
    , this);


init([ create: () => new TopBar() ,  create: () => new StatusBar() ,  create: () => new MainArea() ]);

【讨论】:

以上是关于如何在 TypeScript 中键入带有类的数组?的主要内容,如果未能解决你的问题,请参考以下文章

如何在带有 TypeScript 的 React-Native 中使用 styled-component 键入无状态功能组件?

如何使用 Typescript 在 mongoose 中正确键入对象 ID 数组

如何使用 Typescript 在 mongoose 中正确键入对象 ID 数组

如何键入 Typescript 数组以仅接受一组特定的值?

如何在带有 React 的 Typescript/JSX 中使用带有箭头函数的泛型?

Object.entries() 和 Object.values() 在 WebStorm/PhpStorm 中没有被键入为数组