TypeScript và khởi tạo trường


251

Cách khởi tạo một lớp mới TStheo cách như vậy (ví dụ trong C#để hiển thị những gì tôi muốn):

// ... some code before
return new MyClass { Field1 = "ASD", Field2 = "QWE" };
// ...  some code after

[sửa]
Khi tôi viết câu hỏi này, tôi là nhà phát triển .NET thuần túy mà không có nhiều kiến ​​thức về JS. Ngoài ra TypeScript là một cái gì đó hoàn toàn mới, được công bố là siêu thay thế dựa trên C # mới của JavaScript. Hôm nay tôi thấy câu hỏi này ngu ngốc đến mức nào.

Dù sao, nếu bất cứ ai vẫn đang tìm kiếm một câu trả lời, xin vui lòng, xem xét các giải pháp có thể dưới đây.

Điều đầu tiên cần lưu ý là trong TS, chúng ta không nên tạo các lớp trống cho các mô hình. Cách tốt hơn là tạo giao diện hoặc loại (tùy theo nhu cầu). Bài viết hay từ Phương châm của Todd: https://ultimatecferences.com/blog/groupes-vs-interfaces-in-typescript

GIẢI PHÁP 1:

type MyType = { prop1: string, prop2: string };

return <MyType> { prop1: '', prop2: '' };

GIẢI PHÁP 2:

type MyType = { prop1: string, prop2: string };

return { prop1: '', prop2: '' } as MyType;

GIẢI PHÁP 3 (khi bạn thực sự cần một lớp học):

class MyClass {
   constructor(public data: { prop1: string, prop2: string }) {}
}
// ...
return new MyClass({ prop1: '', prop2: '' });

hoặc là

class MyClass {
   constructor(public prop1: string, public prop2: string) {}
}
// ...
return new MyClass('', '');

Tất nhiên trong cả hai trường hợp, bạn có thể không cần truyền kiểu thủ công vì chúng sẽ được giải quyết từ kiểu trả về hàm / phương thức.


9
"Giải pháp" bạn vừa thêm vào câu hỏi của bạn không phải là TypeScript hoặc JavaScript hợp lệ. Nhưng nó có giá trị để chỉ ra rằng đó là điều trực quan nhất để thử.
Jacob Foshee

1
@JacobFoshee không hợp lệ JavaScript? Xem Công cụ phát triển Chrome của tôi: i.imgur.com/vpathu6.png (nhưng Visual Studio Code hoặc bất kỳ TypeScript nào khác chắc chắn sẽ phàn nàn)
Mars Robertson

5
@MichalStefanow OP đã chỉnh sửa câu hỏi sau khi tôi đăng bình luận đó. Anh ấy đã córeturn new MyClass { Field1: "ASD", Field2: "QWE" };
Jacob Foshee

Câu trả lời:


90

Cập nhật

Kể từ khi viết câu trả lời này, những cách tốt hơn đã đưa ra. Xin vui lòng xem các câu trả lời khác dưới đây có nhiều phiếu hơn và câu trả lời tốt hơn. Tôi không thể xóa câu trả lời này vì nó được đánh dấu là chấp nhận.


Câu trả lời cũ

Có một vấn đề trên bảng mã TypeScript mô tả điều này: Hỗ trợ cho các trình khởi tạo đối tượng .

Như đã nêu, bạn đã có thể thực hiện điều này bằng cách sử dụng các giao diện trong TypeScript thay vì các lớp:

interface Name {
    first: string;
    last: string;
}
class Person {
    name: Name;
    age: number;
}

var bob: Person = {
    name: {
        first: "Bob",
        last: "Smith",
    },
    age: 35,
};

81
Trong ví dụ của bạn, bob không phải là một thể hiện của lớp Person. Tôi không thấy cách này tương đương với ví dụ C #.
Jack Wester

3
Tên giao diện tốt hơn để được bắt đầu bằng chữ "I"
Kiarash

16
1. Person không phải là một lớp và 2. @JackWester đúng, bob không phải là một cá thể. Hãy thử cảnh báo (bob instanceof Person); Trong ví dụ mã này, Person chỉ dành cho mục đích Xác nhận Loại.
Jacques

15
Tôi đồng ý với Jack và Jaques, và tôi nghĩ rằng nó đáng để lặp lại. Bob của bạn thuộc loại Person , nhưng nó hoàn toàn không phải là một ví dụ Person. Hãy tưởng tượng rằng Personthực sự sẽ là một lớp với một hàm tạo phức tạp và một loạt các phương thức, cách tiếp cận này sẽ nằm trên mặt của nó. Thật tốt khi một nhóm người thấy cách tiếp cận của bạn hữu ích, nhưng nó không phải là một giải pháp cho câu hỏi như đã nêu và bạn cũng có thể sử dụng một Lớp người trong ví dụ của bạn thay vì giao diện, nó sẽ là cùng một loại.
Alex

26
Tại sao đây là một câu trả lời được chấp nhận khi nó rõ ràng sai?
Hendrik Wiese

447

Cập nhật ngày 07/12/2016: Bản thảo 2.1 giới thiệu các loại đã ánh xạ và cung cấp Partial<T>, cho phép bạn làm điều này ....

class Person {
    public name: string = "default"
    public address: string = "default"
    public age: number = 0;

    public constructor(init?:Partial<Person>) {
        Object.assign(this, init);
    }
}

let persons = [
    new Person(),
    new Person({}),
    new Person({name:"John"}),
    new Person({address:"Earth"}),    
    new Person({age:20, address:"Earth", name:"John"}),
];

Câu trả lời gốc:

Cách tiếp cận của tôi là xác định một fieldsbiến riêng biệt mà bạn chuyển đến hàm tạo. Mẹo là xác định lại tất cả các trường lớp cho trình khởi tạo này là tùy chọn. Khi đối tượng được tạo (với mặc định của nó), bạn chỉ cần gán đối tượng khởi tạo vào this;

export class Person {
    public name: string = "default"
    public address: string = "default"
    public age: number = 0;

    public constructor(
        fields?: {
            name?: string,
            address?: string,
            age?: number
        }) {
        if (fields) Object.assign(this, fields);
    }
}

hoặc làm thủ công (an toàn hơn một chút):

if (fields) {
    this.name = fields.name || this.name;       
    this.address = fields.address || this.address;        
    this.age = fields.age || this.age;        
}

sử dụng:

let persons = [
    new Person(),
    new Person({name:"Joe"}),
    new Person({
        name:"Joe",
        address:"planet Earth"
    }),
    new Person({
        age:5,               
        address:"planet Earth",
        name:"Joe"
    }),
    new Person(new Person({name:"Joe"})) //shallow clone
]; 

và đầu ra giao diện điều khiển:

Person { name: 'default', address: 'default', age: 0 }
Person { name: 'Joe', address: 'default', age: 0 }
Person { name: 'Joe', address: 'planet Earth', age: 0 }
Person { name: 'Joe', address: 'planet Earth', age: 5 }
Person { name: 'Joe', address: 'default', age: 0 }   

Điều này cung cấp cho bạn sự khởi tạo an toàn và tài sản cơ bản, nhưng tất cả đều tùy chọn và có thể không theo thứ tự. Bạn nhận được mặc định của lớp một mình nếu bạn không vượt qua một lĩnh vực.

Bạn cũng có thể trộn nó với các tham số constructor cần thiết - dán fieldsở cuối.

Tôi gần giống với phong cách C # như bạn nghĩ ( cú pháp trường thực tế đã bị từ chối ). Tôi rất thích trình khởi tạo trường thích hợp, nhưng không có vẻ như nó sẽ xảy ra.

Để so sánh, nếu bạn sử dụng phương pháp truyền, đối tượng trình khởi tạo của bạn phải có TẤT CẢ các trường cho loại bạn đang truyền tới, cộng với không nhận bất kỳ hàm cụ thể nào của lớp (hoặc đạo hàm) do chính lớp đó tạo ra.


1
+1. Điều này thực sự tạo ra một thể hiện của một lớp (mà hầu hết các giải pháp này không có), giữ tất cả các chức năng bên trong hàm tạo (không có Object.create hoặc cruft khác bên ngoài) và nêu rõ tên thuộc tính thay vì dựa vào thứ tự param ( sở thích cá nhân của tôi ở đây). Mặc dù vậy, nó làm mất an toàn kiểu / biên dịch giữa các thông số và thuộc tính.
Câu đố

1
@ user1817787 có lẽ bạn nên xác định bất cứ điều gì có mặc định là tùy chọn trong chính lớp, nhưng chỉ định mặc định. Sau đó, không sử dụng Partial<>, chỉ Person- điều đó sẽ yêu cầu bạn vượt qua một đối tượng có các trường bắt buộc. có nghĩa là, hãy xem ở đây để biết các ý tưởng (Xem Pick) giới hạn Ánh xạ loại cho các trường nhất định.
Meirion Hughes

2
Đây thực sự nên là câu trả lời. Đó là giải pháp tốt nhất cho vấn đề này.
Ascherer

9
đó là đoạn mã VÀNGpublic constructor(init?:Partial<Person>) { Object.assign(this, init); }
kuncevic.dev

3
Nếu Object.assign hiển thị lỗi giống như đối với tôi, vui lòng xem câu trả lời SO này: stackoverflow.com/a/38860354/2621693
Jim Yarbro

27

Dưới đây là một giải pháp kết hợp một ứng dụng ngắn hơn Object.assignđể mô hình chặt chẽ hơn mô hình ban đầu C#.

Nhưng trước tiên, hãy xem xét các kỹ thuật được cung cấp cho đến nay, bao gồm:

  1. Sao chép các hàm tạo chấp nhận một đối tượng và áp dụng nó vào Object.assign
  2. Một Partial<T>mẹo thông minh trong trình xây dựng sao chép
  3. Sử dụng "đúc" chống lại POJO
  4. Tận dụng Object.createthay vìObject.assign

Tất nhiên, mỗi người đều có ưu / nhược điểm của họ. Sửa đổi một lớp mục tiêu để tạo một hàm tạo sao chép có thể không phải luôn luôn là một tùy chọn. Và "truyền" sẽ mất bất kỳ chức năng nào liên quan đến loại mục tiêu. Object.createcó vẻ ít hấp dẫn hơn vì nó đòi hỏi một bản đồ mô tả thuộc tính khá dài dòng.

Câu trả lời ngắn gọn, đa mục đích

Vì vậy, đây là một cách tiếp cận khác có phần đơn giản hơn, duy trì định nghĩa kiểu và các nguyên mẫu hàm liên quan và mô hình chặt chẽ hơn mô hình dự định C#:

const john = Object.assign( new Person(), {
    name: "John",
    age: 29,
    address: "Earth"
});

Đó là nó. Bổ sung duy nhất trên C#mẫu là Object.assigncùng với 2 dấu ngoặc đơn và dấu phẩy. Kiểm tra ví dụ hoạt động dưới đây để xác nhận nó duy trì các nguyên mẫu hàm của kiểu. Không có nhà xây dựng cần thiết, và không có thủ thuật thông minh.

Ví dụ làm việc

Ví dụ này cho thấy cách khởi tạo một đối tượng bằng cách sử dụng xấp xỉ trình C#khởi tạo trường:

class Person {
    name: string = '';
    address: string = '';
    age: number = 0;

    aboutMe() {
        return `Hi, I'm ${this.name}, aged ${this.age} and from ${this.address}`;
    }
}

// typescript field initializer (maintains "type" definition)
const john = Object.assign( new Person(), {
    name: "John",
    age: 29,
    address: "Earth"
});

// initialized object maintains aboutMe() function prototype
console.log( john.aboutMe() );


Cũng giống như thế này, có thể tạo một đối tượng người dùng từ một đối tượng javascript
Dave Keane

1
6 năm sau tôi đã quên điều này, nhưng tôi vẫn thích nó. Cảm ơn một lần nữa.
PRman

25

Bạn có thể ảnh hưởng đến một đối tượng ẩn danh được đúc trong loại lớp của bạn. Phần thưởng : Trong phòng thu trực quan, bạn được lợi từ intellisense theo cách này :)

var anInstance: AClass = <AClass> {
    Property1: "Value",
    Property2: "Value",
    PropertyBoolean: true,
    PropertyNumber: 1
};

Biên tập:

CẢNH BÁO Nếu lớp có các phương thức, thể hiện của lớp bạn sẽ không nhận được chúng. Nếu AClass có một hàm tạo, nó sẽ không được thực thi. Nếu bạn sử dụng thể hiện AClass, bạn sẽ nhận được sai.

Tóm lại, bạn nên sử dụng giao diện và không phải lớp . Việc sử dụng phổ biến nhất là cho mô hình miền được khai báo là Plain Old Object. Thật vậy, đối với mô hình miền, bạn nên sử dụng giao diện tốt hơn thay vì lớp. Các giao diện được sử dụng tại thời gian biên dịch để kiểm tra kiểu và không giống như các lớp, các giao diện được loại bỏ hoàn toàn trong quá trình biên dịch.

interface IModel {
   Property1: string;
   Property2: string;
   PropertyBoolean: boolean;
   PropertyNumber: number;
}

var anObject: IModel = {
     Property1: "Value",
     Property2: "Value",
     PropertyBoolean: true,
     PropertyNumber: 1
 };

20
Nếu AClasscó phương thức, anInstancesẽ không có được chúng.
Đức ông

2
Ngoài ra nếu AClass có hàm tạo, nó sẽ không được thực thi.
Michael Erickson

1
Ngoài ra nếu bạn anInstance instanceof AClasssẽ nhận được falsetrong thời gian chạy.
Lucero

15

Tôi đề nghị một cách tiếp cận không yêu cầu Bản đánh máy 2.1:

class Person {
    public name: string;
    public address?: string;
    public age: number;

    public constructor(init:Person) {
        Object.assign(this, init);
    }

    public someFunc() {
        // todo
    }
}

let person = new Person(<Person>{ age:20, name:"John" });
person.someFunc();

những điểm chính:

  • Bản thảo 2.1 không bắt buộc, Partial<T>không bắt buộc
  • Nó hỗ trợ các chức năng (so với xác nhận kiểu đơn giản không hỗ trợ các chức năng)

1
Nó không tôn trọng các lĩnh vực bắt buộc: new Person(<Person>{});(vì đúc) và cũng rõ ràng; sử dụng một phần <T> hỗ trợ các chức năng. Cuối cùng, nếu bạn có các trường bắt buộc (cộng với các hàm nguyên mẫu), bạn sẽ cần phải thực hiện: init: { name: string, address?: string, age: number }và bỏ các diễn viên.
Meirion Hughes

Ngoài ra, khi chúng tôi nhận được ánh xạ loại có điều kiện, bạn sẽ chỉ có thể ánh xạ các hàm thành các phần và giữ nguyên các thuộc tính. :)
Meirion Hughes

Thay vì tinh vi classsau đó var, nếu tôi làm var dog: {name: string} = {name: 'will be assigned later'};, nó biên dịch và hoạt động. Bất kỳ thiếu sót hoặc vấn đề? Ồ, dogcó phạm vi rất nhỏ và cụ thể, chỉ có một trường hợp.
Jeb50

11

Tôi sẽ có xu hướng làm theo cách này, sử dụng (tùy chọn) các thuộc tính tự động và mặc định. Bạn đã không đề xuất rằng hai trường là một phần của cấu trúc dữ liệu, vì vậy đó là lý do tại sao tôi chọn cách này.

Bạn có thể có các thuộc tính trên lớp và sau đó gán chúng theo cách thông thường. Và rõ ràng là chúng có thể hoặc không được yêu cầu, vì vậy đó cũng là một thứ khác. Chỉ là đây là đường cú pháp tốt đẹp như vậy.

class MyClass{
    constructor(public Field1:string = "", public Field2:string = "")
    {
        // other constructor stuff
    }
}

var myClass = new MyClass("ASD", "QWE");
alert(myClass.Field1); // voila! statement completion on these properties

6
Lời xin lỗi chân thành của tôi. Nhưng bạn không thực sự "hỏi về công cụ khởi tạo trường", do đó, thật tự nhiên khi cho rằng bạn có thể quan tâm đến các cách khác để làm mới một lớp học trong TS. Bạn có thể cung cấp thêm một chút thông tin trong câu hỏi của mình nếu bạn đã sẵn sàng để tải xuống.
Ralph Lavelle

Xây dựng +1 là cách để đi nơi có thể; nhưng trong trường hợp bạn có nhiều lĩnh vực và chỉ muốn khởi tạo một vài trong số chúng, tôi nghĩ câu trả lời của tôi làm cho mọi việc dễ dàng hơn.
Meirion Hughes

1
Nếu bạn có nhiều trường, việc khởi tạo một đối tượng như thế này sẽ khá khó sử dụng vì bạn sẽ chuyển cho nhà xây dựng một bức tường của các giá trị không tên. Điều đó không có nghĩa là phương pháp này không có giá trị; nó sẽ được sử dụng tốt nhất cho các đối tượng đơn giản với một vài trường. Hầu hết các ấn phẩm nói rằng khoảng bốn hoặc năm tham số trong chữ ký của thành viên là giới hạn trên. Chỉ cần chỉ ra điều này bởi vì tôi tìm thấy một liên kết đến giải pháp này trên blog của ai đó trong khi tìm kiếm các công cụ khởi tạo TS. Tôi có các đối tượng với hơn 20 trường cần được khởi tạo để kiểm tra đơn vị.
Dustin Cleveland

11

Trong một số tình huống có thể được chấp nhận để sử dụng Object.create. Tham chiếu Mozilla bao gồm một polyfill nếu bạn cần khả năng tương thích ngược hoặc muốn cuộn chức năng khởi tạo của riêng bạn.

Áp dụng cho ví dụ của bạn:

Object.create(Person.prototype, {
    'Field1': { value: 'ASD' },
    'Field2': { value: 'QWE' }
});

Kịch bản hữu ích

  • Bài kiểm tra đơn vị
  • Khai báo nội tuyến

Trong trường hợp của tôi, tôi thấy điều này hữu ích trong các bài kiểm tra đơn vị vì hai lý do:

  1. Khi kiểm tra kỳ vọng tôi thường muốn tạo ra một vật thể mỏng như một kỳ vọng
  2. Các khung kiểm tra đơn vị (như Jasmine) có thể so sánh nguyên mẫu đối tượng ( __proto__) và không thử nghiệm. Ví dụ:
var actual = new MyClass();
actual.field1 = "ASD";
expect({ field1: "ASD" }).toEqual(actual); // fails

Đầu ra của lỗi thử nghiệm đơn vị sẽ không mang lại manh mối về những gì không khớp.

  1. Trong các bài kiểm tra đơn vị tôi có thể chọn lọc về những trình duyệt tôi hỗ trợ

Cuối cùng, giải pháp được đề xuất tại http://typescript.codeplex.com/workitem/334 không hỗ trợ khai báo kiểu json nội tuyến. Ví dụ: phần sau không biên dịch:

var o = { 
  m: MyClass: { Field1:"ASD" }
};

9

Tôi muốn một giải pháp có thể như sau:

  • Tất cả các đối tượng dữ liệu được yêu cầu và phải được điền bởi hàm tạo.
  • Không cần phải cung cấp mặc định.
  • Có thể sử dụng các chức năng bên trong lớp.

Đây là cách tôi làm:

export class Person {
  id!: number;
  firstName!: string;
  lastName!: string;

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  constructor(data: OnlyData<Person>) {
    Object.assign(this, data);
  }
}

const person = new Person({ id: 5, firstName: "John", lastName: "Doe" });
person.getFullName();

Tất cả các thuộc tính trong hàm tạo là bắt buộc và không được bỏ qua nếu không có lỗi trình biên dịch.

Nó phụ thuộc vào các OnlyDatabộ lọc ra getFullName()khỏi các thuộc tính cần thiết và nó được định nghĩa như vậy:

// based on : https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c
type FilterFlags<Base, Condition> = { [Key in keyof Base]: Base[Key] extends Condition ? never : Key };
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
type SubType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;
type OnlyData<T> = SubType<T, (_: any) => any>;

Những hạn chế hiện tại của cách này:

  • Yêu cầu TypeScript 2.8
  • Các lớp học với getters / setters

Câu trả lời này có vẻ gần với lý tưởng nhất đối với tôi. Tự hỏi liệu chúng ta có thể làm tốt hơn với bản thảo 3+
RyanM

1
Theo như tôi có thể nói đây là cách tốt nhất cho đến ngày hôm nay. Cảm ơn bạn.
florian norbert bepunkt

Có một vấn đề với giải pháp này, @VitalyB: Ngay khi các phương thức có tham số, thì điều này bị phá vỡ: Trong khi getFullName () {return "bar"} hoạt động, getFullName (str: string): chuỗi {return str} không hoạt động
florian norbert bepunkt

@floriannorbertbepunkt Chính xác thì cái gì không hiệu quả với bạn? Nó dường như đang hoạt động tốt đối với tôi ...
rfgamaral

3

Đây là một giải pháp khác:

return {
  Field1 : "ASD",
  Field2 : "QWE" 
} as myClass;

2

Bạn có thể có một lớp với các trường tùy chọn (được đánh dấu bằng?) Và một hàm tạo nhận một thể hiện của cùng một lớp.

class Person {
    name: string;     // required
    address?: string; // optional
    age?: number;     // optional

    constructor(person: Person) {
        Object.assign(this, person);
    }
}

let persons = [
    new Person({ name: "John" }),
    new Person({ address: "Earth" }),    
    new Person({ age: 20, address: "Earth", name: "John" }),
];

Trong trường hợp này, bạn sẽ không thể bỏ qua các trường bắt buộc. Điều này cho phép bạn kiểm soát chi tiết đối với việc xây dựng đối tượng.

Bạn có thể sử dụng hàm tạo với loại Phần như đã lưu ý trong các câu trả lời khác:

public constructor(init?:Partial<Person>) {
    Object.assign(this, init);
}

Vấn đề là tất cả các lĩnh vực trở thành tùy chọn và nó không được mong muốn trong hầu hết các trường hợp.


1

Cách dễ nhất để làm điều này là với kiểu đúc.

return <MyClass>{ Field1: "ASD", Field2: "QWE" };

7
Thật không may, (1) đây không phải là kiểu truyền, mà là một kiểu xác nhận thời gian biên dịch , (2) câu hỏi hỏi "làm thế nào để bắt đầu một lớp mới " (nhấn mạnh của tôi) và cách tiếp cận này sẽ không thực hiện được. Sẽ rất tuyệt nếu TypeScript có tính năng này, nhưng thật không may, đó không phải là trường hợp.
John Weisz

định nghĩa của MyClassđâu
Jeb50

0

Nếu bạn đang sử dụng một phiên bản cũ của bản thảo <2.1 thì bạn có thể sử dụng tương tự như sau đây về cơ bản là đúc bất kỳ đối tượng nào để nhập:

const typedProduct = <Product>{
                    code: <string>product.sku
                };

LƯU Ý: Sử dụng phương pháp này chỉ tốt cho các mô hình dữ liệu vì nó sẽ loại bỏ tất cả các phương thức trong đối tượng. Về cơ bản, nó đúc bất kỳ đối tượng nào cho một đối tượng đánh máy


-1

nếu bạn muốn tạo cá thể mới mà không đặt giá trị ban đầu khi thể hiện

1- bạn phải sử dụng lớp không giao diện

2- bạn phải đặt giá trị ban đầu khi tạo lớp

export class IStudentDTO {
 Id:        number = 0;
 Name:      string = '';


student: IStudentDTO = new IStudentDTO();
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.