TypeScript dan inisialisasi bidang

252

Cara init kelas baru TSsedemikian rupa (contoh C#untuk menunjukkan apa yang saya inginkan):

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

[Sunting]
Ketika saya menulis pertanyaan ini saya murni pengembang NET. Tanpa banyak pengetahuan JS. Juga TypeScript adalah sesuatu yang sama sekali baru, diumumkan sebagai superset berbasis C # baru dari JavaScript. Hari ini saya melihat betapa bodohnya pertanyaan ini.

Pokoknya jika ada yang masih mencari jawaban, silakan lihat solusi yang mungkin di bawah ini.

Hal pertama yang perlu diperhatikan adalah dalam TS kita tidak harus membuat kelas kosong untuk model. Cara yang lebih baik adalah dengan membuat antarmuka atau tipe (tergantung kebutuhan). Artikel bagus dari Todd Motto: https://ultimatecourses.com/blog/classes-vs-interfaces-in-typescript

SOLUSI 1:

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

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

SOLUSI 2:

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

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

SOLUSI 3 (ketika Anda benar-benar membutuhkan kelas):

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

atau

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

Tentu saja dalam kedua kasus Anda mungkin tidak perlu tipe casting secara manual karena mereka akan diselesaikan dari tipe fungsi / metode pengembalian.

Nickon
sumber
9
"Solusi" yang baru saja Anda tambahkan ke pertanyaan Anda bukan TypeScript atau JavaScript yang valid. Tetapi penting untuk menunjukkan bahwa itu adalah hal yang paling intuitif untuk dicoba.
Jacob Foshee
1
@JacobFoshee bukan JavaScript yang valid? Lihat Alat Dev Chrome saya: i.imgur.com/vpathu6.png (tetapi Visual Studio Code atau TypeScript lainnya yang berserakan pasti akan mengeluh)
Mars Robertson
5
@MichalStefanow OP mengedit pertanyaan setelah saya memposting komentar itu. Dia memang memilikireturn new MyClass { Field1: "ASD", Field2: "QWE" };
Jacob Foshee

Jawaban:

90

Memperbarui

Sejak menulis jawaban ini, cara yang lebih baik telah muncul. Silakan lihat jawaban lain di bawah ini yang memiliki lebih banyak suara dan jawaban yang lebih baik. Saya tidak dapat menghapus jawaban ini karena sudah ditandai diterima.


Jawaban lama

Ada masalah pada codeplex TypeScript yang menjelaskan ini: Dukungan untuk inisialisasi objek .

Seperti yang dinyatakan, Anda sudah bisa melakukan ini dengan menggunakan antarmuka dalam TypeScript daripada kelas:

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

var bob: Person = {
    name: {
        first: "Bob",
        last: "Smith",
    },
    age: 35,
};
Wouter de Kort
sumber
81
Dalam contoh Anda, bob bukanlah turunan dari Person kelas. Saya tidak melihat bagaimana ini setara dengan contoh C #.
Jack Wester
3
nama antarmuka lebih baik dimulai dengan
huruf
16
1. Orang bukan kelas dan 2.JackWester benar, bob bukan turunan dari Orang. Coba waspada (contoh orang); Dalam contoh kode ini, Orang ada untuk tujuan Ketegasan Jenis saja.
Jacques
15
Saya setuju dengan Jack dan Jaques, dan saya pikir itu layak diulang. Bob Anda adalah tipe Person , tetapi sama sekali bukan turunan dari Person. Bayangkan bahwa Personsebenarnya akan menjadi kelas dengan konstruktor yang kompleks dan banyak metode, pendekatan ini akan jatuh datar di wajahnya. Baik bahwa sekelompok orang menganggap pendekatan Anda bermanfaat, tetapi itu bukan solusi untuk pertanyaan seperti yang dinyatakan dan Anda bisa menggunakan Person kelas dalam contoh Anda alih-alih antarmuka, itu akan menjadi tipe yang sama.
Alex
26
Mengapa ini merupakan jawaban yang diterima padahal jelas salah?
Hendrik Wiese
447

Diperbarui 07/12/2016: Script 2.1 memperkenalkan Jenis dan menyediakan DipetakanPartial<T> , yang memungkinkan Anda untuk melakukan ini ....

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"}),
];

Jawaban asli:

Pendekatan saya adalah mendefinisikan fieldsvariabel terpisah yang Anda berikan ke konstruktor. Caranya adalah mendefinisikan ulang semua bidang kelas untuk penginisialisasi ini sebagai opsional. Ketika objek dibuat (dengan default-nya), Anda cukup menetapkan objek initialiser ke 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);
    }
}

atau melakukannya secara manual (sedikit lebih aman):

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

pemakaian:

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

dan output konsol:

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 }   

Ini memberi Anda keamanan dasar dan inisialisasi properti, tetapi semuanya opsional dan dapat rusak. Anda mendapatkan standar kelas dibiarkan sendiri jika Anda tidak lulus bidang.

Anda juga dapat mencampurnya dengan parameter konstruktor yang diperlukan juga - tetap fieldsdi ujung.

Tentang sedekat mungkin dengan gaya C # yang akan saya dapatkan ( sintaksis lapangan-init sebenarnya ditolak ). Saya lebih suka penginisialisasi bidang yang tepat, tetapi sepertinya belum akan terjadi.

Sebagai perbandingan, jika Anda menggunakan pendekatan casting, objek initializer Anda harus memiliki SEMUA bidang untuk tipe yang Anda casting, plus tidak mendapatkan fungsi spesifik kelas (atau derivasi) yang dibuat oleh kelas itu sendiri.

Meirion Hughes
sumber
1
+1. Ini benar-benar membuat turunan dari kelas (yang sebagian besar dari solusi ini tidak), menyimpan semua fungsi di dalam konstruktor (tidak ada Object.create atau penggerutu lainnya di luar), dan secara eksplisit menyatakan nama properti alih-alih mengandalkan param pemesanan ( preferensi pribadi saya di sini). Itu kehilangan tipe / kompilasi keamanan antara params dan properti, meskipun.
Fiddles
1
@ user1817787 Anda mungkin akan lebih baik mendefinisikan apa pun yang memiliki default sebagai opsional di kelas itu sendiri, tetapi menetapkan default. Maka jangan gunakan Partial<>, hanya Person- yang akan mengharuskan Anda untuk melewati objek yang memiliki bidang yang diperlukan. yang mengatakan, lihat di sini untuk ide (Lihat Pilih) yang membatasi Pemetaan Tipe untuk bidang tertentu.
Meirion Hughes
2
Ini benar-benar harus menjadi jawabannya. Ini solusi terbaik untuk masalah ini.
Ascherer
9
itu adalah sepotong kode EMASpublic constructor(init?:Partial<Person>) { Object.assign(this, init); }
kuncevic.dev
3
Jika Object.assign menunjukkan kesalahan seperti itu untuk saya, silakan lihat jawaban SO ini: stackoverflow.com/a/38860354/2621693
Jim Yarbro
27

Di bawah ini adalah solusi yang menggabungkan aplikasi yang lebih pendek Object.assignuntuk lebih memodelkan C#pola aslinya .

Tapi pertama-tama, mari kita tinjau teknik yang ditawarkan sejauh ini, yang meliputi:

  1. Salin konstruktor yang menerima objek dan menerapkannya ke Object.assign
  2. Partial<T>Trik yang cerdik dalam copy constructor
  3. Penggunaan "casting" terhadap POJO
  4. Memanfaatkan Object.createbukannyaObject.assign

Tentu saja, masing-masing memiliki pro / kontra. Memodifikasi kelas target untuk membuat copy constructor mungkin tidak selalu menjadi pilihan. Dan "casting" kehilangan fungsi yang terkait dengan tipe target. Object.createtampaknya kurang menarik karena membutuhkan peta deskriptor properti yang agak verbose.

Jawaban Singkat, Tujuan Umum

Jadi, inilah pendekatan lain yang agak sederhana, mempertahankan definisi tipe dan prototipe fungsi terkait, dan lebih dekat memodelkan C#pola yang dimaksud :

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

Itu dia. Satu-satunya tambahan di atas C#pola adalah Object.assignbersama dengan 2 tanda kurung dan koma. Lihatlah contoh kerja di bawah ini untuk mengonfirmasi bahwa ia memelihara prototipe fungsi tipe itu. Tidak diperlukan konstruktor, dan tidak ada trik pintar.

Contoh Kerja

Contoh ini menunjukkan cara menginisialisasi objek menggunakan perkiraan C#penginisialisasi bidang:

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

Jonathan B.
sumber
Seperti ini juga, dapat membuat objek pengguna dari objek javascript
Dave Keane
1
6½ tahun kemudian saya lupa ini, tetapi saya masih menyukainya. Terima kasih lagi.
PRMan
25

Anda dapat memengaruhi objek anonim yang dicetak dalam tipe kelas Anda. Bonus : Di studio visual, Anda mendapat manfaat dari intellisense dengan cara ini :)

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

Edit:

PERINGATAN Jika kelas memiliki metode, turunan kelas Anda tidak akan mendapatkannya. Jika AClass memiliki konstruktor, itu tidak akan dieksekusi. Jika Anda menggunakan instance dari AClass, Anda akan mendapatkan false.

Kesimpulannya, Anda harus menggunakan antarmuka dan bukan kelas . Penggunaan paling umum adalah untuk model domain yang dideklarasikan sebagai Plain Old Objects. Memang, untuk model domain Anda sebaiknya menggunakan antarmuka daripada kelas. Antarmuka digunakan pada waktu kompilasi untuk memeriksa jenis dan tidak seperti kelas, antarmuka sepenuhnya dihapus selama kompilasi.

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

var anObject: IModel = {
     Property1: "Value",
     Property2: "Value",
     PropertyBoolean: true,
     PropertyNumber: 1
 };
rhhainaut
sumber
20
Jika AClassberisi metode, anInstancetidak akan mendapatkannya.
Monsinyur
2
Juga jika AClass memiliki konstruktor, itu tidak akan dieksekusi.
Michael Erickson
1
Juga jika Anda melakukannya, anInstance instanceof AClassAnda akan mendapatkan falsepada saat runtime.
Lucero
15

Saya menyarankan pendekatan yang tidak memerlukan naskah 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();

poin kunci:

  • Naskah 2.1 tidak diperlukan, Partial<T>tidak wajib
  • Ini mendukung fungsi (dibandingkan dengan pernyataan tipe sederhana yang tidak mendukung fungsi)
VeganHunter
sumber
1
Itu tidak menghormati bidang wajib: new Person(<Person>{});(karena casting) dan harus jelas juga; menggunakan fungsi pendukung <T> Sebagian. Pada akhirnya, jika Anda memiliki bidang yang diperlukan (ditambah fungsi prototipe), Anda harus melakukan: init: { name: string, address?: string, age: number }dan melepaskan pemeran.
Meirion Hughes
Juga ketika kita mendapatkan pemetaan tipe bersyarat Anda akan dapat memetakan fungsi-fungsi hanya sebagian, dan menjaga properti seperti apa adanya. :)
Meirion Hughes
Alih-alih canggih classitu var, jika saya melakukannya var dog: {name: string} = {name: 'will be assigned later'};, itu mengkompilasi dan karya. Adakah kekurangan atau masalah? Oh, dogmemiliki cakupan yang sangat kecil dan spesifik, artinya hanya satu contoh.
Jeb50
11

Saya akan lebih cenderung melakukannya dengan cara ini, menggunakan properti dan default otomatis (opsional). Anda belum menyarankan bahwa kedua bidang adalah bagian dari struktur data, jadi itu sebabnya saya memilih cara ini.

Anda dapat memiliki properti di kelas dan kemudian menetapkannya dengan cara biasa. Dan jelas mereka mungkin atau mungkin tidak diminta, jadi itu sesuatu yang lain juga. Hanya saja ini adalah gula sintaksis yang bagus.

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
Ralph Lavelle
sumber
6
Permintaan maaf terdalam saya Tetapi Anda tidak benar-benar "bertanya tentang inisialisasi lapangan", jadi wajar untuk berasumsi bahwa Anda mungkin tertarik dengan cara-cara alternatif untuk memulai kelas di TS. Anda mungkin memberikan sedikit informasi lebih banyak dalam pertanyaan Anda jika Anda siap untuk melakukan downvote.
Ralph Lavelle
+1 konstruktor adalah cara untuk pergi jika memungkinkan; tetapi dalam kasus di mana Anda memiliki banyak bidang dan ingin hanya menginisialisasi beberapa dari mereka, saya pikir jawaban saya membuat semuanya lebih mudah.
Meirion Hughes
1
Jika Anda memiliki banyak bidang, menginisialisasi objek seperti ini akan sangat sulit karena Anda akan melewati konstruktor dengan dinding nilai tanpa nama. Itu tidak berarti metode ini tidak memiliki prestasi; itu akan lebih baik digunakan untuk objek sederhana dengan beberapa bidang. Sebagian besar publikasi mengatakan bahwa sekitar empat atau lima parameter dalam tanda tangan anggota adalah batas atas. Hanya menunjukkan ini karena saya menemukan tautan ke solusi ini di blog seseorang saat mencari inisialisasi TS. Saya memiliki objek dengan 20 bidang yang perlu diinisialisasi untuk tes unit.
Dustin Cleveland
11

Dalam beberapa skenario mungkin dapat diterima untuk digunakan Object.create. Referensi Mozilla menyertakan polyfill jika Anda memerlukan kompatibilitas kembali atau ingin menjalankan fungsi initializer Anda sendiri.

Diterapkan pada contoh Anda:

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

Skenario yang Berguna

  • Tes Unit
  • Deklarasi sebaris

Dalam kasus saya, saya menemukan ini berguna dalam unit test karena dua alasan:

  1. Saat menguji ekspektasi, saya sering ingin membuat objek ramping sebagai harapan
  2. Kerangka uji unit (seperti Jasmine) dapat membandingkan prototipe objek ( __proto__) dan gagal dalam tes. Sebagai contoh:
var actual = new MyClass();
actual.field1 = "ASD";
expect({ field1: "ASD" }).toEqual(actual); // fails

Output dari kegagalan unit test tidak akan menghasilkan petunjuk tentang apa yang tidak cocok.

  1. Dalam unit test saya bisa selektif tentang browser apa yang saya dukung

Akhirnya, solusi yang diajukan di http://typescript.codeplex.com/workitem/334 tidak mendukung inline json-style declaration. Misalnya, yang berikut ini tidak dikompilasi:

var o = { 
  m: MyClass: { Field1:"ASD" }
};
Yakub Foshee
sumber
9

Saya menginginkan solusi yang memiliki yang berikut:

  • Semua objek data diperlukan dan harus diisi oleh konstruktor.
  • Tidak perlu memberikan default.
  • Dapat menggunakan fungsi di dalam kelas.

Inilah cara saya melakukannya:

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

Semua properti di konstruktor adalah wajib dan tidak boleh dihilangkan tanpa kesalahan kompilator.

Itu tergantung pada OnlyDatayang menyaring getFullName()keluar dari properti yang diperlukan dan didefinisikan seperti:

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

Batasan saat ini dengan cara ini:

  • Membutuhkan TypeScript 2.8
  • Kelas dengan getter / setter
VitalyB
sumber
Jawaban ini tampaknya paling mendekati ideal bagi saya. Ingin tahu apakah kita bisa melakukan yang lebih baik dengan naskah 3+
RyanM
1
Sejauh yang saya tahu ini adalah cara terbaik untuk maju hari ini. Terima kasih.
florian norbert bepunkt
Ada satu masalah dengan solusi ini, @VitalyB: Segera setelah metode memiliki parameter, istirahat ini: Sementara getFullName () {return "bar"} bekerja, getFullName (str: string): string {return str} tidak berfungsi
florian norbert bepunkt
@ floriannorbertbepunkt Apa sebenarnya yang tidak bekerja untuk Anda? Tampaknya bekerja dengan baik untuk saya ...
rfgamaral
3

Ini adalah solusi lain:

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

Anda bisa memiliki kelas dengan bidang opsional (ditandai dengan?) Dan konstruktor yang menerima turunan dari kelas yang sama.

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" }),
];

Dalam hal ini, Anda tidak akan dapat menghilangkan bidang yang diperlukan. Ini memberi Anda kontrol halus atas konstruksi objek.

Anda bisa menggunakan konstruktor dengan tipe Partial seperti yang tercantum dalam jawaban lain:

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

Masalahnya adalah bahwa semua bidang menjadi opsional dan tidak diinginkan dalam banyak kasus.

Alex
sumber
1

Cara termudah untuk melakukan ini adalah dengan tipe casting.

return <MyClass>{ Field1: "ASD", Field2: "QWE" };
Peter Pompeii
sumber
7
Sayangnya, (1) ini bukan tipe-casting, tetapi tipe-waktu- pernyataan-pernyataan , (2) pertanyaan yang diajukan "bagaimana init kelas baru " (penekanan tambang), dan pendekatan ini akan gagal mencapai itu. Akan lebih baik jika TypeScript memiliki fitur ini, tetapi sayangnya, bukan itu masalahnya.
John Weisz
dimana definisi MyClass?
Jeb50
0

Jika Anda menggunakan naskah naskah versi lama <2.1 maka Anda dapat menggunakan yang serupa dengan yang berikut ini yang pada dasarnya melakukan pengecoran terhadap objek apa pun yang diketik:

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

CATATAN: Menggunakan metode ini hanya baik untuk model data karena akan menghapus semua metode dalam objek. Ini pada dasarnya casting objek apa pun ke objek yang diketik

Mohsen Tabareh
sumber
-1

jika Anda ingin membuat instance baru tanpa menetapkan nilai awal saat instance

1- Anda harus menggunakan kelas bukan antarmuka

2 - Anda harus menetapkan nilai awal saat membuat kelas

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


student: IStudentDTO = new IStudentDTO();
Hosam Hemaily
sumber