Genel sınıfta type parametresinden yeni bir nesne oluşturma


145

Genel sınıfta bir type parametresi yeni bir nesne oluşturmaya çalışıyorum. Benim sınıfımda View, ben tür parametreleri olarak geçirilen Jenerik türden nesnelerin 2 listeleri var, ama marka çalıştığınızda new TGridView(), typescript diyor ki:

'TGridView sembolü bulunamadı

Bu kod:

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;
        }
    }
}

Genel türden nesneler oluşturabilir miyim?

Yanıtlar:


96

Derlenen JavaScript tüm tür bilgileri silindiğinden, Tbir nesneyi yeni oluşturmak için kullanamazsınız .

Bunu yapıcıya yazarak jenerik olmayan bir şekilde yapabilirsiniz.

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();

Türleri sıkılaştırmak için jenerikler kullanarak bu örneği genişletebilirsiniz:

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

Genel kod içinde yeni bir nesne oluşturmak için, yapıcı işlevine göre türe başvurmanız gerekir. Bunu yazmak yerine:

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

Bunu yazmanız gerekiyor:

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

var classA: ClassA = activator(ClassA);

Bu soruya bakın: Sınıf Bağımsız Değişkeniyle Genel Tür Çıkarım


35
Biraz geç (ama kullanışlı) yorum - eğer new()type: {new(...args : any[]): T ;}
kurucunuz argüman alırsa

1
@Yoda IActivatable kendi kendine oluşturulmuş bir arayüzdür, diğer soruya bakın: stackoverflow.com/questions/24677592/…
Jamie

1
Bu nasıl anlaşılmalıdır { new(): T ;}. Bu bölümü öğrenmekte zorlanıyorum.
abitcode

1
Bu (ve buradaki diğer tüm çözümler), jenerik belirtmenin üstünde sınıfı geçmenizi gerektirir. Bu çılgın ve gereksiz. Yani bir temel sınıfım varsa ClassA<T>ve onu genişletirsem , bu çözümlerden ClassB extends ClassA<MyClass>de geçmek zorundayım new () => T = MyClassya da hiçbiri işe yaramıyor.
AndrewBenjamin

@AndrewBenjamin Peki öneriniz nedir? Hiçbir şey başlatmaya çalışmıyorum (aslında öğrenmek istiyorum) ama bunu paylaşmak için başka bir yol biliyorum hacky ve gereksiz infers diyorlar :)
perry

37

Tüm tür bilgileri JavaScript tarafında silinir ve bu nedenle T'yi @Sohnee durumlarında olduğu gibi yenemezsiniz, ancak yapıcıya geçirilen parametre girilmesini tercih ederim:

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;
}

Nesnesi herhangi bir parametre alabilecektir, ancak T tipi sadece bir daktilo referansıdır ve bir kurucu aracılığıyla oluşturulamaz. Yani, herhangi bir sınıf nesnesi oluşturmaz.


6
Dikkat edin , bu işe yarayabilir, ancak ortaya çıkan nesne Tür olmayacaktır T, bu nedenle tanımlanan alıcılar / ayarlayıcılar Tçalışmayabilir.
Daniel Ormeño

@ DanielOrmeño açıklamak ister misiniz? Bununla ne demek istiyorsun? Kodum bu pasajı kullanarak çalışıyor gibi görünüyor. Teşekkürler.
eestein

Javascript yorumlanmış bir dildir ve ecmascript'in henüz türlere veya sınıflara referansları yoktur. Yani T sadece daktilo için bir referanstır.
jales cardoso

Bu JavaScript'te bile YANLIŞ bir cevaptır. A'nız varsa class Foo{ method() { return true; }, {} 'yi T yapmak size bu yöntemi vermeyecektir!
JCKödel

9

Geç biliyorum ama @ TadasPa'nın cevabı kullanarak biraz ayarlanabilir

TCreator: new() => T

onun yerine

TCreator: { new (): T; }

sonuç böyle görünmeli

class A {
}

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

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

4

Ben bir temel sınıf içinde jenerik örneklemeye çalışıyordu. Yukarıdaki örneklerin hiçbiri, fabrika yöntemini çağırmak için somut bir tip gerektirdikleri için benim için işe yaramadı.

Bir süre bu konuda araştırma yaptıktan ve çevrimiçi bir çözüm bulamadıktan sonra, bunun işe yaradığını gördüm.

 protected activeRow: T = {} as T;

Parçalar:

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

...

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

Hep birlikte

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

Bununla birlikte, gerçek bir örneğe ihtiyacınız varsa şunları kullanmalısınız:

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();

Argümanlarınız varsa kullanabilirsiniz.

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
Bu JavaScript'te bile YANLIŞ bir cevaptır. Eğer bir sınıfınız varsa Foo {method () {return true; }, {} 'yi T yapmak size bu yöntemi vermeyecektir!
JCKödel

Metodunuz yoksa çalışırım. Ancak yöntemleri ile bir nesne varsa ben az önce eklediğim kodu kullanmanız gerekir.
Christian Patzer

2

Bu tür bilgi tutmak için ne yapılır:

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>{}; Bunu kullanın: genellikle EDIT 1 çalışır:

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
Bu aslında yeni bir örneğini örneğini olmaz Tsadece typescript o boş bir nesne yapacak, düşünen tiptedir T. Cevabınızı biraz daha ayrıntılı olarak açıklamak isteyebilirsiniz.
Sandy Gifford

Genelde işe yaradığını söyledim. Tam olarak söylediğin gibi. Derleyici sızlanıyor ve jenerik kodunuz var. Bu örneği, açık yapıcıya ihtiyaç duymadan istediğiniz özellikleri manuel olarak ayarlamak için kullanabilirsiniz.
Bojo

1

Soruyu tam olarak cevaplamıyor, ancak bu tür problemler için güzel bir kütüphane var: https://github.com/typestack/class-transformer (her ne kadar gerçekte mevcut olmadıkları için genel türler için çalışmayacak olsa da) çalışma zamanında (burada tüm çalışmalar sınıf isimleriyle yapılır (sınıf yapıcılarıdır)))

Örneğin:

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

Partiye geç kaldım ama işte böyle çalışıyorum. Diziler için bazı numaralar yapmamız gerekiyor:

   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;
   }

Şöyle kullanın:

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

Geliştirilebilir ama benim ihtiyaçları için çalıştı!


1

Soruyu doğrudan çözdüğünü düşündüğüm için değil, bunu istek üzerine ekliyorum. Benim çözüm SQL veritabanımdan tabloları görüntülemek için bir tablo bileşeni içerir:

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);
    }
}

Bunu kullanmak için, benim veritabanı VW_MyData bir görünüm (veya tablo) var ve bir sorgudan döndürülen her giriş için VW_MyData sınıfının yapıcı vurmak istiyorum varsayalım:

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);
            }
        }
    }
}

Bunun sadece döndürülen değeri Data'ya atamak için istenmesinin nedeni, yapıcısında VW_MyData'nın bazı sütunlarına bir dönüşüm uygulayan bazı kodlara sahip olduğum söylenir:

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}!`;
    }
}

Bu, TypeScript'e gelen tüm verilerimde dönüşümler, doğrulamalar ve başka herhangi bir şey yapmamı sağlıyor. Umarım birisi için bir fikir verir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.