从泛型类中的类型参数创建新对象


144

我正在尝试在我的通用类中创建一个类型参数的新对象。在我的课程中View,我有2个作为类型参数传递的泛型对象列表,但是当我尝试制作时new TGridView(),TypeScript说:

找不到符号'TGridView

这是代码:

module AppFW {
    // Represents a view
    export class View<TFormView extends FormView, TGridView extends GridView> {
        // The list of forms 
        public Forms: { [idForm: string]: TFormView; } = {};

        // The list of grids
        public Grids: { [idForm: string]: TGridView; } = {};

        public AddForm(formElement: HTMLFormElement, dataModel: any, submitFunction?: (e: SubmitFormViewEvent) => boolean): FormView {
            var newForm: TFormView = new TFormView(formElement, dataModel, submitFunction);
            this.Forms[formElement.id] = newForm;
            return newForm;
        }

        public AddGrid(element: HTMLDivElement, gridOptions: any): GridView {
            var newGrid: TGridView = new TGridView(element, gridOptions);
            this.Grids[element.id] = newGrid;
            return newGrid;
        }
    }
}

我可以从通用类型创建对象吗?

Answers:


95

由于已编译的JavaScript删除了所有类型信息,因此您不能使用 T来创建对象。

您可以通过将类型传递到构造函数中来以非泛型方式执行此操作。

class TestOne {
    hi() {
        alert('Hi');
    }
}

class TestTwo {
    constructor(private testType) {

    }
    getNew() {
        return new this.testType();
    }
}

var test = new TestTwo(TestOne);

var example = test.getNew();
example.hi();

您可以使用泛型来扩展该示例以加强类型:

class TestBase {
    hi() {
        alert('Hi from base');
    }
}

class TestSub extends TestBase {
    hi() {
        alert('Hi from sub');
    }
}

class TestTwo<T extends TestBase> {
    constructor(private testType: new () => T) {
    }

    getNew() : T {
        return new this.testType();
    }
}

//var test = new TestTwo<TestBase>(TestBase);
var test = new TestTwo<TestSub>(TestSub);

var example = test.getNew();
example.hi();

144

要在通用代码中创建新对象,您需要通过其构造函数来引用类型。所以不用写这个:

function activatorNotWorking<T extends IActivatable>(type: T): T {
    return new T(); // compile error could not find symbol T
}

您需要编写以下代码:

function activator<T extends IActivatable>(type: { new(): T ;} ): T {
    return new type();
}

var classA: ClassA = activator(ClassA);

看到以下问题: 具有类参数的泛型类型推断


35
有点晚了(但很有用)的注释-如果您的构造函数使用args,那么new()需求也是如此-或更通用的方法:type: {new(...args : any[]): T ;}
Rycochet

1
@Yoda IActivatable是一个自行创建的界面,请参见其他问题:stackoverflow.com/questions/24677592/…–
Jamie

1
应该如何推断{ new(): T ;}。我很难学习这一部分。
abitcode

1
此(以及此处的所有其他解决方案)要求您在指定泛型之前传递该类。这是hacky和多余的。这意味着,如果我有一个基类ClassA<T>并对其进行扩展,那么ClassB extends ClassA<MyClass>我也必须传递new () => T = MyClass这些解决方案,否则这些解决方案都不起作用。
AndrewBenjamin

@AndrewBenjamin那您的建议是什么?不尝试启动任何东西(实际上是想学习),而是称其为hacky和多余的推断表明您知道了您不共享的另一种方法:)
perry

37

所有类型信息都在JavaScript端被擦除,因此您不能像@Sohnee状态那样更新T,但是我更喜欢将类型化参数传递给构造函数:

class A {
}

class B<T> {
    Prop: T;
    constructor(TCreator: { new (): T; }) {
        this.Prop = new TCreator();
    }
}

var test = new B<A>(A);

12
export abstract class formBase<T> extends baseClass {

  protected item = {} as T;
}

它的对象将能够接收任何参数,但是,类型T只是一个打字稿引用,不能通过构造函数创建。也就是说,它将不会创建任何类对象。


6
请注意,这可能会起作用,但是生成的对象将不是Type T,因此在中定义的任何吸气剂/设定剂都T可能不起作用。
DanielOrmeño17年

@DanielOrmeño在乎解释吗?你是什​​么意思?我的代码似乎正在使用此代码段运行。谢谢。
eestein

Javascript是一种解释性语言,而ecmascript尚未引用类型或类。所以T只是打字稿的参考。
杰勒斯·卡多佐

即使在JavaScript中,这也是一个错误的答案。如果您有一个class Foo{ method() { return true; },将{}强制转换为T将不会为您提供此方法!
JCKödel

8

我知道晚了,但是@TadasPa的答案可以通过使用进行一些调整

TCreator: new() => T

代替

TCreator: { new (): T; }

所以结果应该像这样

class A {
}

class B<T> {
    Prop: T;
    constructor(TCreator: new() => T) {
        this.Prop = new TCreator();
    }
}

var test = new B<A>(A);

4

我试图从基类中实例化泛型。上面的示例对我来说都不起作用,因为它们需要具体的类型才能调用工厂方法。

在对此进行了一段时间研究并且无法在线找到解决方案后,我发现这似乎可行。

 protected activeRow: T = {} as T;

碎片:

 activeRow: T = {} <-- activeRow now equals a new object...

...

 as T; <-- As the type I specified. 

全部一起

 export abstract class GridRowEditDialogBase<T extends DataRow> extends DialogBase{ 
      protected activeRow: T = {} as T;
 }

就是说,如果您需要一个实际实例,则应使用:

export function getInstance<T extends Object>(type: (new (...args: any[]) => T), ...args: any[]): T {
      return new type(...args);
}


export class Foo {
  bar() {
    console.log("Hello World")
  }
}
getInstance(Foo).bar();

如果有参数,则可以使用。

export class Foo2 {
  constructor(public arg1: string, public arg2: number) {

  }

  bar() {
    console.log(this.arg1);
    console.log(this.arg2);
  }
}
getInstance(Foo, "Hello World", 2).bar();

2
即使在JavaScript中,这也是一个错误的答案。如果您有一个类Foo {method(){返回true; },将{}强制转换为T不会给您这种方法!
JCKödel

如果您没有方法,我会工作。但是,如果您的对象带有方法,则必须使用我刚刚添加的代码。
Christian Patzer

2

这是我要保留的类型信息:

class Helper {
   public static createRaw<T>(TCreator: { new (): T; }, data: any): T
   {
     return Object.assign(new TCreator(), data);
   }
   public static create<T>(TCreator: { new (): T; }, data: T): T
   {
      return this.createRaw(TCreator, data);
   }
}

...

it('create helper', () => {
    class A {
        public data: string;
    }
    class B {
        public data: string;
        public getData(): string {
            return this.data;
        }
    }
    var str = "foobar";

    var a1 = Helper.create<A>(A, {data: str});
    expect(a1 instanceof A).toBeTruthy();
    expect(a1.data).toBe(str);

    var a2 = Helper.create(A, {data: str});
    expect(a2 instanceof A).toBeTruthy();
    expect(a2.data).toBe(str);

    var b1 = Helper.createRaw(B, {data: str});
    expect(b1 instanceof B).toBeTruthy();
    expect(b1.data).toBe(str);
    expect(b1.getData()).toBe(str);

});

1

我用这个:let instance = <T>{}; 它通常可以编辑1:

export class EntityCollection<T extends { id: number }>{
  mutable: EditableEntity<T>[] = [];
  immutable: T[] = [];
  edit(index: number) {
    this.mutable[index].entity = Object.assign(<T>{}, this.immutable[index]);
  }
}

5
这实际上不会实例化一个新的实例T,只会制造一个TypeScript 认为是类型的空对象T。您可能需要更详细地解释您的答案。
桑迪·吉福德

我说这通常有效。就是你说的那样 编译器没有抱怨,您拥有通用代码。您可以使用此实例来手动设置所需的属性,而无需显式的构造函数。
Bojo

1

尚未完全回答问题,但是有一个不错的库可以解决此类问题:https : //github.com/typestack/class-transformer(尽管它不适用于泛型类型,因为它们实际上并不存在在运行时(这里所有的工作都由类名(它们是类的构造函数)完成)

例如:

import {Type, plainToClass, deserialize} from "class-transformer";

export class Foo
{
    @Type(Bar)
    public nestedClass: Bar;

    public someVar: string;

    public someMethod(): string
    {
        return this.nestedClass.someVar + this.someVar;
    }
}

export class Bar
{
    public someVar: string;
}

const json = '{"someVar": "a", "nestedClass": {"someVar": "B"}}';
const optionA = plainToClass(Foo, JSON.parse(json));
const optionB = deserialize(Foo, json);

optionA.someMethod(); // works
optionB.someMethod(); // works

1

我参加聚会迟到了,但这就是我工作的方式。对于数组,我们需要做一些技巧:

   public clone<T>(sourceObj: T): T {
      var cloneObj: T = {} as T;
      for (var key in sourceObj) {
         if (sourceObj[key] instanceof Array) {
            if (sourceObj[key]) {
               // create an empty value first
               let str: string = '{"' + key + '" : ""}';
               Object.assign(cloneObj, JSON.parse(str))
               // update with the real value
               cloneObj[key] = sourceObj[key];
            } else {
               Object.assign(cloneObj, [])
            }
         } else if (typeof sourceObj[key] === "object") {
            cloneObj[key] = this.clone(sourceObj[key]);
         } else {
            if (cloneObj.hasOwnProperty(key)) {
               cloneObj[key] = sourceObj[key];
            } else { // insert the property
               // need create a JSON to use the 'key' as its value
               let str: string = '{"' + key + '" : "' + sourceObj[key] + '"}';
               // insert the new field
               Object.assign(cloneObj, JSON.parse(str))
            }
         }
      }
      return cloneObj;
   }

像这样使用它:

  let newObj: SomeClass = clone<SomeClass>(someClassObj);

可以改进,但可以满足我的需求!


1

我是根据要求添加的,不是因为我认为它可以直接解决问题。我的解决方案涉及一个表组件,用于显示我的SQL数据库中的表:

export class TableComponent<T> {

    public Data: T[] = [];

    public constructor(
        protected type: new (value: Partial<T>) => T
    ) { }

    protected insertRow(value: Partial<T>): void {
        let row: T = new this.type(value);
        this.Data.push(row);
    }
}

为了使用此功能,假设我在数据库VW_MyData中有一个视图(或表),并且想为查询返回的每个条目命中VW_MyData类的构造函数:

export class MyDataComponent extends TableComponent<VW_MyData> {

    public constructor(protected service: DataService) {
        super(VW_MyData);
        this.query();
    }

    protected query(): void {
        this.service.post(...).subscribe((json: VW_MyData[]) => {
            for (let item of json) {
                this.insertRow(item);
            }
        }
    }
}

这比仅将返回值分配给Data更为可取,原因是说我有一些代码将转换应用于VW_MyData构造函数中的某些列:

export class VW_MyData {
    
    public RawColumn: string;
    public TransformedColumn: string;


    public constructor(init?: Partial<VW_MyData>) {
        Object.assign(this, init);
        this.TransformedColumn = this.transform(this.RawColumn);
    }

    protected transform(input: string): string {
        return `Transformation of ${input}!`;
    }
}

这使我可以对输入到TypeScript的所有数据执行转换,验证以及其他任何操作。希望它能为某人提供一些见识。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.