สร้างวัตถุใหม่จากพารามิเตอร์ประเภทในชั้นเรียนทั่วไป


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

คุณสามารถขยายตัวอย่างนี้โดยใช้ generics เพื่อกระชับประเภท:

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 ข้อเสนอแนะของคุณคืออะไร? ไม่พยายามที่จะเริ่มต้นอะไร (ที่จริงต้องการที่จะเรียนรู้) แต่เรียกมันว่า hacky และอนุมานซ้ำซ้อนคุณรู้วิธีการอื่นว่าคุณไม่ได้ใช้งานร่วมกัน :)
เพอร์รี

37

ข้อมูลประเภททั้งหมดจะถูกลบในด้าน JavaScript และดังนั้นคุณจึงไม่สามารถสร้าง T ขึ้นมาใหม่ได้เช่นเดียวกับ @Sohnee States แต่ฉันอยากจะให้พารามิเตอร์ที่พิมพ์ลงในตัวสร้าง:

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 เป็นเพียงการอ้างอิง typescript และไม่สามารถสร้างผ่านคอนสตรัค นั่นคือมันจะไม่สร้างวัตถุคลาสใด ๆ


6
ระวังสิ่งนี้อาจใช้งานได้ แต่วัตถุที่เป็นผลลัพธ์จะไม่เป็นประเภทTดังนั้นตัวรับ / setters ใด ๆ ที่กำหนดไว้ใน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
นี้จะไม่จริงยกตัวอย่างตัวอย่างใหม่ของTมันก็จะทำให้วัตถุที่ว่างเปล่าที่ typescript คิดว่าTเป็นประเภท คุณอาจต้องการอธิบายคำตอบของคุณอย่างละเอียดยิ่งขึ้น
Sandy Gifford

ฉันบอกว่ามันใช้งานได้ทั่วไป มันเป็นอย่างที่คุณพูด คอมไพเลอร์ไม่ส่งเสียงครวญครางและคุณมีรหัสทั่วไป คุณสามารถใช้อินสแตนซ์นี้เพื่อตั้งค่าคุณสมบัติที่คุณต้องการโดยไม่ต้องมี Constructor ที่ชัดเจน
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);
            }
        }
    }
}

เหตุผลนี้เป็นที่พึงปรารถนามากกว่าเพียงแค่การกำหนดค่าที่ส่งคืนไปยังข้อมูลคือว่าฉันมีรหัสบางอย่างที่ใช้การแปลงไปยังคอลัมน์ของ 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.