Tạo một đối tượng mới từ tham số kiểu trong lớp chung


144

Tôi đang cố gắng tạo một đối tượng mới của một tham số loại trong lớp chung của tôi. Trong lớp của tôi View, tôi có 2 danh sách các đối tượng thuộc loại chung được truyền dưới dạng tham số loại, nhưng khi tôi cố gắng thực hiện new TGridView(), TypeScript nói:

Không thể tìm thấy biểu tượng 'TGridView

Đây là mã:

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

Tôi có thể tạo các đối tượng từ một loại chung không?

Câu trả lời:


95

Vì JavaScript đã biên dịch có tất cả các loại thông tin đã bị xóa, bạn không thể sử dụng Tđể làm mới một đối tượng.

Bạn có thể làm điều này theo cách không chung chung bằng cách chuyển kiểu vào hàm tạo.

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

Bạn có thể mở rộng ví dụ này bằng cách sử dụng thuốc generic để thắt chặt các loại:

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

Để tạo một đối tượng mới trong mã chung, bạn cần tham khảo kiểu theo hàm tạo của nó. Vì vậy, thay vì viết này:

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

Bạn cần viết cái này:

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

var classA: ClassA = activator(ClassA);

Xem câu hỏi này: Suy luận kiểu chung với đối số lớp


35
Một số nhận xét muộn (nhưng hữu ích) - nếu nhà xây dựng của bạn tranh luận thì cũng new()cần phải có - hoặc chung chung hơn:type: {new(...args : any[]): T ;}
Rycochet

1
@Yoda IActivitable là giao diện tự tạo, xem câu hỏi khác: stackoverflow.com/questions/24677592/
Jamie

1
Làm thế nào điều này nên được suy luận { new(): T ;}. Tôi đang gặp khó khăn để học phần này.
mã abit

1
Điều này (và tất cả các giải pháp khác ở đây) yêu cầu bạn vượt qua trong lớp trên đầu chỉ định chung. Đây là hacky và dư thừa. Điều đó có nghĩa là nếu tôi có một lớp cơ sở ClassA<T>và mở rộng nó ClassB extends ClassA<MyClass>, tôi cũng phải truyền vào new () => T = MyClasshoặc không có giải pháp nào trong số này hoạt động.
AndrewBenjamin

@AndrewBenjamin Đề xuất của bạn là gì? Không cố gắng để bắt đầu bất cứ điều gì (thực sự muốn học) nhưng gọi đó là hacky và infers dư thừa có biết một cách khác mà bạn không chia sẻ :)

37

Tất cả thông tin loại được xóa trong phía JavaScript và do đó bạn không thể tạo mới T giống như các trạng thái @Sohnee, nhưng tôi muốn có tham số được nhập vào hàm tạo:

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

Đối tượng của nó sẽ có thể nhận bất kỳ tham số nào, tuy nhiên, loại T chỉ là tham chiếu bản thảo và không thể được tạo thông qua một hàm tạo. Đó là, nó sẽ không tạo ra bất kỳ đối tượng lớp nào.


6
Coi chừng , điều này có thể hoạt động nhưng đối tượng kết quả sẽ không phải là Loại T, do đó, bất kỳ getters / setters nào được xác định trong Tcó thể không hoạt động.
Daniel Ormeño

@ DanielOrmeño quan tâm giải thích? Ý bạn là như thế nào? Mã của tôi dường như đang hoạt động bằng đoạn mã này. Cảm ơn.
eestein

Javascript là một ngôn ngữ được giải thích và ecmascript chưa có tham chiếu đến các loại hoặc các lớp. Vì vậy, T chỉ là một tài liệu tham khảo cho bản thảo.
jales cardoso

Đây là một câu trả lời SAU, ngay cả trong JavaScript. Nếu bạn có class Foo{ method() { return true; }, việc truyền {} thành T sẽ không cung cấp cho bạn phương thức này!
JCKödel

8

Tôi biết muộn nhưng câu trả lời của @ TadasPa có thể được điều chỉnh một chút bằng cách sử dụng

TCreator: new() => T

thay vì

TCreator: { new (): T; }

vì vậy kết quả sẽ như thế này

class A {
}

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

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

4

Tôi đã cố gắng khởi tạo cái chung từ trong một lớp cơ sở. Không có ví dụ nào ở trên làm việc cho tôi vì họ yêu cầu một loại cụ thể để gọi phương thức nhà máy.

Sau khi nghiên cứu một lúc về điều này và không thể tìm ra giải pháp trực tuyến, tôi phát hiện ra rằng điều này dường như hoạt động.

 protected activeRow: T = {} as T;

Các mảnh:

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

...

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

Tất cả cùng nhau

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

Điều đó nói rằng, nếu bạn cần một ví dụ thực tế, bạn nên sử dụng:

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

Nếu bạn có đối số, bạn có thể sử dụng.

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
Đây là một câu trả lời SAU, ngay cả trong JavaScript. Nếu bạn có một lớp Foo {phương thức () {return true; }, truyền {} đến T sẽ không cung cấp cho bạn phương thức này!
JCKödel

Tôi làm việc nếu bạn không có phương pháp. Tuy nhiên, nếu bạn có một đối tượng với các phương thức, bạn phải sử dụng mã tôi vừa thêm.
Christian Patzer

2

Đây là những gì tôi làm để giữ lại thông tin loại:

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

tôi sử dụng cái này: let instance = <T>{}; nó thường hoạt động EDIT 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
Điều này sẽ không thực sự khởi tạo một thể hiện mới T, nó sẽ tạo ra một đối tượng trống mà TypeScript nghĩ là thuộc loại T. Bạn có thể muốn giải thích câu trả lời của bạn chi tiết hơn một chút.
Sandy Gifford

Tôi nói nó thường hoạt động. Nó chính xác như bạn nói. Trình biên dịch không rên rỉ và bạn có mã chung. Bạn có thể sử dụng thể hiện này để đặt thủ công các thuộc tính bạn muốn mà không cần đến hàm tạo rõ ràng.
Bojo

1

Không hoàn toàn trả lời câu hỏi, nhưng, có một thư viện hay cho những vấn đề đó: https://github.com/typestack/ class-transformer (mặc dù nó không hoạt động cho các loại chung chung, vì chúng không thực sự tồn tại tại thời gian chạy (ở đây tất cả các công việc được thực hiện với tên lớp (là các hàm tạo của lớp)))

Ví dụ:

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

Tôi đến bữa tiệc muộn nhưng đây là cách tôi làm cho nó hoạt động. Đối với mảng chúng ta cần thực hiện một số thủ thuật:

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

Sử dụng nó như thế này:

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

Nó có thể được cải thiện nhưng làm việc cho nhu cầu của tôi!


1

Tôi đang thêm điều này theo yêu cầu, không phải vì tôi nghĩ rằng nó trực tiếp giải quyết câu hỏi. Giải pháp của tôi liên quan đến một thành phần bảng để hiển thị các bảng từ cơ sở dữ liệu SQL của tôi:

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

Để sử dụng tính năng này, giả sử tôi có chế độ xem (hoặc bảng) trong cơ sở dữ liệu của mình, VW_MyData và tôi muốn nhấn hàm tạo của lớp VW_MyData cho mỗi mục nhập được trả về từ truy vấn:

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

Lý do điều này được mong muốn chỉ đơn giản là gán giá trị được trả về cho Dữ liệu, nói rằng tôi có một số mã áp dụng một phép chuyển đổi cho một số cột của VW_MyData trong hàm tạo của nó:

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

Điều này cho phép tôi thực hiện các phép biến đổi, xác nhận hợp lệ và bất cứ điều gì khác trên tất cả dữ liệu của tôi đến với TypeScript. Hy vọng nó cung cấp một số cái nhìn sâu sắc cho một ai đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.