391 Stimmen

TypeScript und Feldinitialisierungen

Wie initiiert man eine neue Klasse in TS auf diese Weise (Beispiel in C#, um zu zeigen, was ich will):

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

645voto

Meirion Hughes Punkte 23026

Aktualisiert am 12.07.2016: Typescript 2.1 führt Mapped Types ein und bietet Partial<T> , die Ihnen dies ermöglicht....

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

Ursprüngliche Antwort:

Mein Ansatz ist die Definition einer separaten fields Variable, die Sie an den Konstruktor übergeben. Der Trick besteht darin, alle Klassenfelder für diesen Initialisierer als optional umzudefinieren. Wenn das Objekt (mit seinen Standardwerten) erstellt ist, weisen Sie das Initialisierungsobjekt einfach der Variablen 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);
    }
}

oder machen Sie es manuell (etwas sicherer):

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

Verwendung:

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

und Konsolenausgaben:

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 }   

Dies gibt Ihnen grundlegende Sicherheit und Eigenschaftsinitialisierung, aber es ist alles optional und kann außer der Reihe sein. Sie erhalten die Standardwerte der Klasse, wenn Sie kein Feld übergeben.

Sie können es auch mit erforderlichen Konstruktorparametern mischen - bleiben Sie bei fields am Ende.

So nah am C#-Stil wie möglich, denke ich ( die eigentliche field-init-Syntax wurde abgelehnt ). Ich würde einen richtigen Feldinitialisierer bevorzugen, aber es sieht nicht so aus, als würde das noch passieren.

Zum Vergleich: Wenn Sie den Casting-Ansatz verwenden, muss Ihr Initialisierungsobjekt über ALLE Felder des Typs verfügen, auf den Sie casten, und Sie erhalten keine klassenspezifischen Funktionen (oder Ableitungen), die von der Klasse selbst erstellt wurden.

117voto

Wouter de Kort Punkte 37970

Update

Seit ich diese Antwort geschrieben habe, haben sich bessere Möglichkeiten ergeben. Bitte beachten Sie die anderen Antworten unten, die mehr Stimmen und eine bessere Antwort haben. Ich kann diese Antwort nicht entfernen, da sie als akzeptiert markiert ist.


Alte Antwort

Es gibt ein Problem auf dem TypeScript-Codeplex, das dies beschreibt: Unterstützung für Objektinitialisierer .

Wie bereits erwähnt, können Sie dies bereits tun, indem Sie in TypeScript Schnittstellen anstelle von Klassen verwenden:

interface Name {
    givenName: string;
    surname: string;
}
class Person {
    name: Name;
    age: number;
}

var bob: Person = {
    name: {
        givenName: "Bob",
        surname: "Smith",
    },
    age: 35,
};

64voto

Jonathan B. Punkte 2344

Im Folgenden wird eine Lösung vorgestellt, die eine kürzere Anwendung von Object.assign um dem Original näher zu kommen C# Muster.

Doch zunächst ein Überblick über die bisher angebotenen Techniken, die da wären

  1. Kopieren Sie Konstruktoren, die ein Objekt akzeptieren und dieses auf Object.assign
  2. Eine clevere Partial<T> Trick innerhalb des Kopierkonstruktors
  3. Verwendung von "Casting" gegen ein POJO
  4. Nutzung von Object.create anstelle von Object.assign

Natürlich haben beide ihre Vor- und Nachteile. Das Ändern einer Zielklasse, um einen Kopierkonstruktor zu erstellen, ist nicht immer eine Option. Und beim "Casting" gehen alle mit dem Zieltyp verbundenen Funktionen verloren. Object.create scheint weniger attraktiv zu sein, da es eine ziemlich ausführliche Eigenschaftsbeschreibung erfordert.

Kürzeste, allgemein anwendbare Antwort

Hier ist also ein weiterer Ansatz, der etwas einfacher ist, die Typdefinition und die zugehörigen Funktionsprototypen beibehält und die beabsichtigte Methode besser abbildet C# Muster:

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

Das war's. Der einzige Zusatz gegenüber dem C# Muster ist Object.assign zusammen mit 2 Klammern und einem Komma. Überprüfen Sie anhand des folgenden Arbeitsbeispiels, ob die Funktionsprototypen des Typs erhalten bleiben. Es sind keine Konstruktoren und keine cleveren Tricks erforderlich.

Arbeitsbeispiel

Dieses Beispiel zeigt, wie man ein Objekt mit einer Annäherung an eine C# Feldinitialisierer:

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

35voto

rdhainaut Punkte 2520

Sie können ein anonymes Objekt beeinflussen, das in Ihren Klassentyp gecastet wurde. Bonus : In Visual Studio, profitieren Sie von intellisense auf diese Weise :)

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

Bearbeiten:

WARNUNG Wenn die Klasse Methoden hat, wird die Instanz Ihrer Klasse diese nicht erhalten. Wenn AClass einen Konstruktor hat, wird dieser nicht ausgeführt. Wenn Sie instanceof AClass verwenden, erhalten Sie false.

Zusammenfassend lässt sich sagen, dass Sie eine Schnittstelle und keine Klasse verwenden sollten. . Am häufigsten wird das Domänenmodell als "Plain Old Objects" deklariert. In der Tat sollten Sie für ein Domänenmodell besser eine Schnittstelle statt einer Klasse verwenden. Schnittstellen werden zur Kompilierungszeit für die Typüberprüfung verwendet und im Gegensatz zu Klassen werden Schnittstellen während der Kompilierung vollständig entfernt.

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

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

22voto

VeganHunter Punkte 4568

Ich schlage einen Ansatz vor, der kein Typescript 2.1 erfordert:

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

wichtige Punkte:

  • Typescript 2.1 nicht erforderlich, Partial<T> nicht erforderlich
  • Es unterstützt Funktionen (im Vergleich zur einfachen Typbehauptung, die keine Funktionen unterstützt)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X