제네릭 클래스의 형식 매개 변수에서 새 개체를 만듭니다.


145

제 일반 클래스에서 유형 매개 변수의 새 객체를 만들려고합니다. 내 클래스 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;
        }
    }
}

제네릭 형식에서 개체를 만들 수 있습니까?

답변:


96

컴파일 된 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
다소 늦었지만 (유용한) 의견-생성자가 인수를 new()요구하는 경우 또는보다 일반적인 요구 사항type: {new(...args : any[]): T ;}
Rycochet

1
@Yoda IActivatable은 자체 생성 된 인터페이스입니다. 다른 질문을 참조하십시오 : stackoverflow.com/questions/24677592/…
Jamie

1
이것이 어떻게 추론되어야 하는가 { new(): T ;}. 이 부분을 배우는 데 어려움을 겪고 있습니다.
abitcode

1
이것 (및 여기의 다른 모든 솔루션)은 제네릭을 지정하는 것 외에도 클래스를 전달해야합니다. 이것은 해킹과 중복입니다. 기본 클래스가 ClassA<T>있고 확장하면 이러한 솔루션이 작동하지 않거나 ClassB extends ClassA<MyClass>전달 new () => T = MyClass하지 않아도됩니다 .
AndrewBenjamin

@AndrewBenjamin 그렇다면 당신의 제안은 무엇입니까? 아무것도 시작하지 않고 (실제로 배우고 싶어) 해킹하고 중복이라고 부르면 공유하지 않는 다른 방법을 알 수 있습니다.)
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
조심 이 작동 할 수 있지만 결과 개체 유형의 습관, T에 정의 된 게터 / 세터 그렇게 T작동하지 않을 수 있습니다.
Daniel Ormeño

@ DanielOrmeño 설명이 필요하세요? 그게 무슨 뜻 이니? 이 코드를 사용하여 코드가 작동하는 것 같습니다. 감사.
eestein

Javascript는 해석 된 언어이며 ecmascript는 아직 유형 또는 클래스에 대한 참조를 가지고 있지 않습니다. 따라서 T는 typescript에 대한 참조 일뿐입니다.
jales cardoso

이것은 JavaScript에서도 잘못된 답변입니다. 가 있으면 class Foo{ method() { return true; }{}을 (를) T로 캐스팅해도이 방법이 제공되지 않습니다!
JCKödel

9

늦었지만 알고 있지만 @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 () 클래스가 있다면 {return 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
이것은 실제로 새로운 인스턴스를 인스턴스화하지 않고 TTypeScript 유형 이라고 생각 하는 빈 객체를 만듭니다 T. 답을 좀 더 자세히 설명하고 싶을 수도 있습니다.
Sandy Gifford

나는 그것이 일반적으로 작동한다고 말했다. 그것은 당신이 말하는 그대로입니다. 컴파일러가 징징 거리지 않고 일반 코드가 있습니다. 이 인스턴스를 사용하면 명시적인 생성자없이 원하는 속성을 수동으로 설정할 수 있습니다.
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로 들어오는 모든 데이터에서 변환, 유효성 검사 및 기타 모든 작업을 수행 할 수 있습니다. 잘만되면 그것은 누군가에게 통찰력을 제공합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.