TypeScript ve alan başlatıcıları


252

Yeni bir sınıfı TSbu şekilde nasıl başlatabilirim ( C#ne istediğimi göstermek için örnek ):

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

[değiştir]
Bu soruyu yazarken ben JS bilgisi olmadan çok saf NET geliştiricisi oldu. Ayrıca TypeScript, JavaScript'in yeni C # tabanlı üst kümesi olarak açıklanan tamamen yeni bir şeydi. Bugün bu sorunun ne kadar aptal olduğunu görüyorum.

Her neyse, hala bir cevap arayan varsa, lütfen aşağıdaki olası çözümlere bakın.

Dikkat edilmesi gereken ilk şey TS'de modeller için boş sınıflar oluşturmamalıyız. Daha iyi bir yol, arayüz veya tür oluşturmaktır (ihtiyaçlara bağlı olarak). Todd Slogan'dan iyi makale: https://ultimatecourses.com/blog/classes-vs-interfaces-in-typescript

ÇÖZÜM 1:

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

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

ÇÖZÜM 2:

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

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

ÇÖZÜM 3 (gerçekten bir sınıfa ihtiyacınız olduğunda):

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

veya

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

Tabii ki her iki durumda da işlev / yöntem dönüş türünden çözülecekleri için döküm türlerine manuel olarak ihtiyacınız olmayabilir.


9
Sorunuza yeni eklediğiniz "çözüm" geçerli bir TypeScript veya JavaScript değil. Ancak denemek için en sezgisel şey olduğunu belirtmek önemlidir.
Jacob Foshee

1
@JacobFoshee geçerli JavaScript değil mi? Chrome Geliştirme Araçlarına bakın: i.imgur.com/vpathu6.png (ancak Visual Studio Code veya diğer herhangi bir TypeScript bağlanması kaçınılmaz olarak şikayet eder)
Mars Robertson

5
@MichalStefanow OP bu yorumu gönderdikten sonra soruyu düzenledi. Yaptıreturn new MyClass { Field1: "ASD", Field2: "QWE" };
Jacob Foshee

Yanıtlar:


90

Güncelleme

Bu cevabı yazdığından beri daha iyi yollar ortaya çıktı. Lütfen aşağıda daha fazla oy ve daha iyi bir cevabı olan diğer cevaplara bakınız. Kabul edildi olarak işaretlendiğinden bu yanıtı kaldıramıyorum.


Eski cevap

TypeScript kod paketinde bunu açıklayan bir sorun var: Nesne başlatıcıları için destek .

Belirtildiği gibi, bunu TypeScript'te sınıflar yerine arabirimler kullanarak zaten yapabilirsiniz:

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

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

81
Örneğinizde bob, Person sınıfının bir örneği değildir. Bunun C # örneğine nasıl eşdeğer olduğunu görmüyorum.
Jack Wester

3
arayüz adları büyük
harf

16
1. Kişi bir sınıf değildir ve 2. @JackWester haklıdır, bob Kişi örneğidir. Uyarıyı deneyin (bob instanceof Person); Bu kod örneğinde, Kişi yalnızca Tür Onaylama amacıyla vardır.
Jacques

15
Jack ve Jaques ile aynı fikirdeyim ve sanırım tekrar etmeye değer. Bob türünde Person , ama hiç bir örneği değil Person. PersonAslında karmaşık bir yapıcıya ve bir grup yönteme sahip bir sınıf olacağını hayal edin , bu yaklaşım yüzüne düz düşecektir. Bir grup insanın yaklaşımınızı yararlı bulması iyidir, ancak belirtildiği gibi soruya bir çözüm değildir ve örneğin bir arayüzde bir arayüz yerine bir Sınıf Kişisi de kullanabilirsiniz, aynı tip olacaktır.
Alex

26
Bu açıkça yanlış olduğunda neden kabul edilmiş bir cevaptır?
Hendrik Wiese

447

Updated 07/12/2016: Daktilo Yazısı 2.1 Eşleştirilmiş Türleri tanıtır ve Partial<T>bunu yapmanızı sağlar ....

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

Orijinal Yanıt:

Benim yaklaşımım yapıcıya ilettiğiniz ayrı bir fieldsdeğişken tanımlamaktır . Hile, bu başlatıcı için tüm sınıf alanlarını isteğe bağlı olarak yeniden tanımlamaktır. Nesne oluşturulduğunda (varsayılanlarıyla), başlangıç ​​nesnesini üzerine atamanız yeterlidir 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);
    }
}

veya manuel olarak yapın (biraz daha güvenli):

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

kullanımı:

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

ve konsol çıkışı:

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 }   

Bu size temel güvenlik ve özellik başlatmayı sağlar, ancak hepsi isteğe bağlıdır ve arızalı olabilir. Bir alanı geçmezseniz, sınıfın varsayılan değerlerini tek başına bırakırsınız.

Ayrıca gerekli kurucu parametreleriyle de karıştırabilirsiniz - fieldsucuna yapıştırabilirsiniz .

Sanırım alacağınız C # stiline yakın ( gerçek field-init sözdizimi reddedildi ). Doğru alan başlatıcıyı çok tercih ederim, ama henüz gerçekleşmeyecek gibi görünmüyor.

Karşılaştırma için, döküm yaklaşımını kullanırsanız, initialiser nesneniz, döküm yaptığınız tür için TÜM alanlara sahip olmalı, ayrıca sınıfın kendisi tarafından oluşturulan sınıfa özgü işlevler (veya türevler) almamalıdır.


1
+1. Bu aslında bir sınıf örneği oluşturur (bu çözümlerin çoğunun yapmadığı), tüm işlevselliği yapıcı içinde tutar (Object.create veya dışarıda başka bir rüşvet yok) ve param sırasına güvenmek yerine özellik adını açıkça belirtir ( burada kişisel tercihim). Yine de, parametreler ve özellikler arasındaki tip / derleme güvenliğini kaybetmez.
Fiddles

1
@ user1817787 muhtemelen sınıfın kendisinde varsayılan olarak isteğe bağlı olan herhangi bir şeyi tanımlamaktan daha iyi olur, ancak bir varsayılan atar. O zaman kullanmayın Partial<>, sadece Person- bu, gerekli alanlara sahip bir nesneyi geçirmenizi gerektirir. Bununla birlikte, Tür Eşlemeyi belirli alanlarla sınırlayan fikirler için (Bkz. Seçim) buraya bakın.
Meirion Hughes

2
Bu gerçekten cevap olmalı. Bu sorun için en iyi çözüm.
Ascherer

9
bu ALTIN ​​kod parçasıpublic constructor(init?:Partial<Person>) { Object.assign(this, init); }
kuncevic.dev


27

Aşağıda, Object.assignorijinal C#kalıbı daha yakından modellemek için daha kısa bir uygulamayı birleştiren bir çözüm bulunmaktadır .

Ama önce, şu ana kadar sunulan teknikleri gözden geçirelim:

  1. Bir nesneyi kabul eden ve bu nesneyi uygulayan yapıcıları kopyalayın Object.assign
  2. Partial<T>Kopya yapıcı içinde zekice bir numara
  3. Bir POJO'ya karşı "döküm" kullanımı
  4. Yararlanma Object.createyerineObject.assign

Tabii ki, her birinin kendi artıları / eksileri var. Bir kopya oluşturucu oluşturmak için hedef sınıfı değiştirmek her zaman bir seçenek olmayabilir. Ve "döküm", hedef türüyle ilişkili tüm işlevleri kaybeder. Object.createoldukça ayrıntılı bir özellik tanımlayıcı haritası gerektirdiği için daha az çekici görünüyor.

En Kısa, Genel Amaçlı Cevap

Yani, burada biraz daha basit olan, tür tanımını ve ilişkili işlev prototiplerini koruyan ve amaçlanan C#deseni daha yakından modelleyen başka bir yaklaşım :

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

Bu kadar. C#Desen üzerindeki tek ekleme Object.assign2 parantez ve virgül ile birlikte yapılır. Türün işlev prototiplerini koruduğunu doğrulamak için aşağıdaki çalışma örneğine bakın. Herhangi bir kurucuya gerek yok ve akıllı numaralar yok.

Çalışma Örneği

Bu örnek, bir C#alan başlatıcısının yaklaşık değerini kullanarak bir nesnenin nasıl başlatılacağını gösterir :

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


Bunun gibi, bir javascript nesnesinden bir kullanıcı nesnesi oluşturabilir
Dave Keane

1
6½ yıl sonra bunu unuttum, ama yine de beğendim. Tekrar teşekkürler.
PRMan

25

Sınıf türünüze dökülen anonim bir nesneyi etkileyebilirsiniz. Bonus : Görsel stüdyoda, bu şekilde zekânızdan faydalanırsınız :)

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

Düzenle:

UYARI Sınıfta yöntemler varsa, sınıfınızın örneği bunları alamaz. AClass bir yapıcıya sahipse, çalıştırılmaz. AClass örneğini kullanırsanız, yanlış elde edersiniz.

Sonuç olarak, sınıf değil arayüz kullanmalısınız . En yaygın kullanım Düz Eski Nesneler olarak bildirilen etki alanı modelidir. Gerçekten de, etki alanı modeli için sınıf yerine arabirimi daha iyi kullanmalısınız. Arabirimler tip denetimi için derleme zamanında kullanılır ve sınıfların aksine, arabirimler derleme sırasında tamamen kaldırılır.

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

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

20
Eğer AClassyer alan yöntemlerin, anInstanceonları almak olmaz.
Monsignor

2
Ayrıca AClass bir yapıcıya sahipse, çalıştırılmaz.
Michael Erickson

1
Ayrıca yaparsanız çalışma zamanında anInstance instanceof AClassalırsınız false.
Lucero

15

Ben daktilo 2.1 gerektirmeyen bir yaklaşım öneriyoruz:

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

anahtar noktaları:

  • Daktilo 2.1 gerekli değil, Partial<T> değil, gerekli değil
  • Fonksiyonları destekler (fonksiyonları desteklemeyen basit tip iddiasına kıyasla)

1
Zorunlu alanlara saygı duymaz: new Person(<Person>{});(çünkü döküm) ve açık olmak; Kısmi <T> kullanımı işlevleri destekler. Nihayetinde, gerekli alanlara (artı prototip işlevlerine) sahipseniz, yapmanız gereken: init: { name: string, address?: string, age: number }ve atmayı bırakmak.
Meirion Hughes

Ayrıca koşullu tür eşlemesi aldığımızda, yalnızca işlevleri kısmi olarak eşleyebilir ve özellikleri olduğu gibi tutabilirsiniz. :)
Meirion Hughes

classO zaman sofistike yerine, varyaparsam var dog: {name: string} = {name: 'will be assigned later'};derler ve çalışır. Herhangi bir eksiklik veya sorun var mı? Oh, dogçok küçük bir kapsamı vardır ve spesifiktir, yani sadece bir örnek.
Jeb50

11

Ben (isteğe bağlı olarak) otomatik özellikleri ve varsayılanları kullanarak, bu şekilde yapmak daha eğilimli olurdu. İki alanın bir veri yapısının parçası olmasını önermediniz, bu yüzden bu şekilde seçtim.

Sınıftaki özelliklere sahip olabilir ve daha sonra bunları normal şekilde atayabilirsiniz. Ve tabii ki gerekli olabilir veya olmayabilir de, bu da başka bir şey. Sadece bu kadar güzel bir sözdizimsel şeker.

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
En derin özür dilerim. Ama aslında "saha başlatıcıları hakkında soru sormadınız", bu yüzden TS'de bir sınıf oluşturmanın alternatif yollarıyla ilgilenebileceğinizi varsaymak doğaldır. Aşağı oy vermeye hazırsanız, sorunuzda biraz daha fazla bilgi verebilirsiniz.
Ralph Lavelle

+1 yapıcısı mümkün olan yere gitmenin yoludur; ancak çok fazla alanınızın olduğu ve yalnızca bazılarını başlatmak istediğiniz durumlarda, cevabımın işleri kolaylaştırdığını düşünüyorum.
Meirion Hughes

1
Birçok alanınız varsa, böyle bir nesneyi başlatmak, kurucuya isimsiz değerlerin bir duvarını geçireceğiniz için oldukça hantal olacaktır. Bu, bu yöntemin bir değeri olmadığı anlamına gelmez; en az birkaç alana sahip basit nesneler için kullanılır. Çoğu yayın, bir üyenin imzasındaki yaklaşık dört veya beş parametrenin üst sınır olduğunu söyler. Sadece bunu belirtmek çünkü TS başlatıcıları ararken birinin blogunda bu çözümün bağlantısını buldum. Birim testleri için başlatılması gereken 20+ alana sahip nesnelerim var.
Dustin Cleveland

11

Bazı senaryolarda kullanmak kabul edilebilir Object.create . Mozilla referansı, geri uyumluluğa ihtiyacınız varsa veya kendi başlatıcı işlevinizi döndürmek istiyorsanız bir çoklu dolgu içerir.

Örneğinize uygulanır:

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

Yararlı Senaryolar

  • Birim Testleri
  • Satır içi bildirim

Benim durumumda bunu iki nedenden dolayı birim testlerinde yararlı buldum:

  1. Beklentileri test ederken genellikle beklenti olarak ince bir nesne oluşturmak istiyorum
  2. Birim test çerçeveleri (Jasmine gibi) nesne prototipini ( __proto__) karşılaştırabilir ve testi geçemez. Örneğin:
var actual = new MyClass();
actual.field1 = "ASD";
expect({ field1: "ASD" }).toEqual(actual); // fails

Birim test başarısızlığının çıktısı, neyin uyuşmadığı hakkında bir ipucu vermeyecektir.

  1. Birim testlerinde hangi tarayıcıları desteklediğim konusunda seçici olabilirim

Son olarak, http://typescript.codeplex.com/workitem/334 adresinde önerilen çözüm satır içi json tarzı bildirimi desteklemez. Örneğin, aşağıdakiler derlenmez:

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

9

Aşağıdakilere sahip bir çözüm istedim:

  • Tüm veri nesneleri gereklidir ve yapıcı tarafından doldurulmalıdır.
  • Varsayılanlar sağlamanıza gerek yoktur.
  • Sınıf içindeki fonksiyonları kullanabilir.

İşte bunu yapmanın yolu:

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

Yapıcıdaki tüm özellikler zorunludur ve bir derleyici hatası olmadan atlanamaz.

Gerekli özelliklerin OnlyDatafiltrelenmesine bağlıdır getFullName()ve şöyle tanımlanır:

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

Bu şekilde mevcut sınırlamalar:

  • TypeScript 2.8 gerektirir
  • Alıcıları / ayarlayıcıları olan sınıflar

Bu cevap benim için ideale en yakın görünüyor. 3+
yazıtipi

1
Anlayabildiğim kadarıyla bu, bugünden itibaren en iyi yol. Teşekkür ederim.
florian norbert bepunkt

Bu çözümle ilgili bir sorun var, @VitalyB: Yöntemler parametreye sahip olur olmaz, bu kesiliyor: getFullName () {return "bar"} çalışırken, getFullName (str: string): string {return str} çalışmıyor
florian norbert bepunkt

@floriannorbertbepunkt Sizin için tam olarak işe yaramayan nedir? Benim için iyi çalışıyor gibi görünüyor ...
rfgamaral

3

Bu başka bir çözüm:

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

2

İsteğe bağlı alanlara (? İle işaretli) sahip bir sınıfınız ve aynı sınıfın bir örneğini alan bir kurucunuz olabilir.

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

Bu durumda, gerekli alanları atlayamazsınız. Bu, nesne yapısı üzerinde hassas kontrol sağlar.

Yapıcıyı diğer yanıtlarda belirtildiği gibi Kısmi türle kullanabilirsiniz:

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

Sorun, tüm alanların isteğe bağlı hale gelmesi ve çoğu durumda istenmemesi.


1

Bunu yapmanın en kolay yolu tip dökümdür.

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

7
Ne yazık ki, (1) Bu döküm tip değildir, ancak bir derleme zamanı tip iddiası , (2) soru sorulduğunda "Yeni init nasıl sınıfını " (vurgu benim) ve bu yaklaşım bunu başarmak için başarısız olur. TypeScript'in bu özelliğe sahip olması kesinlikle güzel olurdu, ancak maalesef durum böyle değil.
John Weisz

tanımı MyClassnedir?
Jeb50

0

Yazı tipi <2.1'in eski bir sürümünü kullanıyorsanız, temelde yazılan herhangi bir nesneye döküm yapan aşağıdakine benzer şekilde kullanabilirsiniz:

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

NOT: Bu yöntemi kullanmak yalnızca veri modelleri için iyidir çünkü nesnedeki tüm yöntemleri kaldırır. Temelde herhangi bir nesneyi yazılı bir nesneye döküyor


-1

başlangıç ​​değeri ayarlanmamış yeni bir örnek oluşturmak istiyorsanız

1- sınıf değil arayüz kullanmalısınız

2- sınıf oluştururken başlangıç ​​değerini ayarlamanız gerekir

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


student: IStudentDTO = new IStudentDTO();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.