JavaScript TypeScript - Construktor Verständnis

Peter P.

Cadet 3rd Year
Registriert
Mai 2017
Beiträge
32
Hallo Community,

ich bin während eines Tutorials über folgende Klasse gestolpert

Code:
export class QuestionBase<T> {
  value: T;
  key: string;
  label: string;
  required: boolean;
  order: number;
  controlType: string;


  constructor(options: {
    value?: T,
    key?: string,
    label?: string,
    required?: boolean,
    order?: number,
    controlType?: string
  } = {}) {
    this.value = options.value;
    this.key = options.key || '';
    this.label = options.label || '';
    this.required = !!options.required;
    this.order = options.order === undefined ? 1 : options.order;
    this.controlType = options.controlType || '';
  }
}

Diesen verstehe ich nicht genau

Zeile1 : Was bedeutet <T>hinter dem Klassennamen?
Zeile2: Was ist der Datentyp T?

Constructor Zeile 10 - 17

Ich lesen diesen wie folgt. Es gibt einen Parameter mit den namen options dieser enthält den Aufbau den danach definierten Objekttypen, alle Eigenschaften dieses Objekttypen sind optional. Was in Zeile 17 Passiert ist mir unklar.

im Körper des Konstruktors werden die Attribute zugewiesen, aber warum hat Zeile 21. ein doppeltes Ausrufezeichen? Zum negieren reicht doch ein Einfaches.

Viele Grüße
Peter P.
 
Zeile 17: Default parameter (hier ein "leeres" objekt)
Funktioniert weil alle Parameter der erwarteten klasse optional sind, koennte mir vorstellen das es mit Serialisierung zusammenhaengt

Zeile 21: Alternative zu: this.required = options.required || false;
Funktioniert weil JS, siehe https://stackoverflow.com/a/858270
 
Also:

Der Konstruktor erwartet ein Objekt vom Typ
Code:
    value?: T,
    key?: string,
    label?: string,
    required?: boolean,
    order?: number,
    controlType?: string
, wobei jedes property des zu übergebenden Objekts ebenfalls Null sein darf. Die Zeile 17 setzt die variable "options" auf ein neues, leeres Objekt, falls kein Parameter an den Konstruktor übergeben werden sollte. Also im Grunde einfach nur ein Standardwert.

Zeile 21 ist eine Möglichkeit, sicher einen Boolean zu erhalten.

Stellen wir uns vor, options.required ist undefined (da es ja als Nullable in der Typdefinition der options-Obkekt definiert ist)
!options.required wäre also true, da undefined zu false gecastet wird.
!!options.required ist demnach !true, also false.


edit: zu langsam -.-
 
Nafi schrieb:
wobei jedes property des zu übergebenden Objekts ebenfalls Null sein darf.
Das ist nicht korrekt. Die Properties mit "?" dürfen `undefined` sein - aber nicht `null`. Kleiner aber feiner Unterschied.

Um `null` zu erlauben müsste man z.B. folgendes schreiben: `key: string | null`
Für `null` oder `undefined` dann so: `key?: string | null` oder `key: string | null | undefined`
 
vielen Dank für eure Informationen und eure Ergänzungen. In einem kurzen Moment dachte ich das ich das mit dem Konstruktor verstanden habe, dann kam mit dieser in die Quere.

Code:
  constructor(options: {} = {}) {
    super(options);
    this.options = options['options'] || [];
  }

Verstehe ich das richtig, dieser Konstruktor nimmt ein Datentyp {} entgegen und speichert in in der Variablen options und wird mit {} initialisiert. Warum macht man das überhaupt? Mit einem leeren Objekt kann man doch wenig anfangen oder?
 
Ja, dein neues Beispiel ist etwas "an TypeScript vorbei".

Im Prinzip steht da:
- Der Konstruktor bekommt 1 optionalen Parameter übergeben. (optional, weil es einen Standardwert gibt)
- Dieser Parameter ist ein object
- Falls der Parameter nicht gesetzt ist, wird der Standardwert (= leeres object) benutzt

Wenn man es halbwegs richtig typisieren will, dann sollte es eher so aussehen:
Code:
constructor(options: { options?: any } = {}) { ... }
Dann kann man auch mit `options.options` drauf zugreifen und muss nicht `options["options"]` benutzen.

Ganz wichtig bei allem was mit TypeScript zu tun hat:
Die Typ-Angaben verändern (bis auf ganz wenige Ausnahmefälle) nichts an der Funktionalität. Die Typen dienen nur dazu, dass der Entwickler den Code besser verstehen kann und um Programmierfehler zu vermeiden.
Du könntest genau so gut überall `any` als Typ setzen und das Programm verhält sich nach wie vor exakt gleich. Aber dann kann man sich TypeScript auch komplett sparen ;)

Soll heißen: Bei vielen Beispielen braucht man nicht nach Sinn und Unsinn der Typen fragen. Manche Entwickler geben nur Typen für öffentliche APIs an und andere schreiben einfach hier und da `any` hin, wenn es zu aufwändig ist, den korrekten Typ in TypeScript zu definieren.
Und gerade wenn man ein altes JS Projekt auf TypeScript umstellt, ist man gezwungen erst mal überall `object` und `any` als Typ anzugeben (oder halt implicit any aktivieren), weil sonst plötzlich der Code nicht mehr kompiliert. Und erst nach und nach kann man dann überall die korrekten Typen angeben.
Es gibt auch diverse große Projekte, wie z.B. das Angular Framework, wo man sich an einigen Stellen fragt, warum hier jetzt keine ordentlichen Typen angegeben wurden.
Und dazu kommt natürlich noch, dass sich TypeScript ständig weiterentwickelt. Alle paar Monate kommen neue Features dazu. Wenn deine Beispiele mit z.B. Typescript 1.0 geschrieben wurden, dann ist es natürlich gut möglich, dass dort diverse Sprach-Features nicht benutzt wurden, weil es sie zu dem Zeitpunkt noch gar nicht gab.

Grundsätzlich halte ich es so mit TypeScript:
- Erst mal (fast) alle "strict" Options aktivieren (siehe: https://www.typescriptlang.org/docs/handbook/compiler-options.html ). Vor allem `noImplicitAny` sollte aktiviert sein. Dann wird man gezwungen sauber zu typisieren. Dadurch wird dann schon mal größtenteils verhindert, dass man uneindeutigen Code schreiben kann.
- Die einzige Option, die gelegentlich stört ist `strictPropertyInitialization`. Aber abgesehen davon kann man eigentlich einfach `strict: true` setzen und gut ist.
- `any` und `object` werden nur in Ausnahmefällen benutzt. In 99% der Fälle kann man die Typen eindeutig angeben.
 
Zuletzt bearbeitet:
Das ist nicht korrekt. Die Properties mit "?" dürfen `undefined` sein - aber nicht `null`. Kleiner aber feiner Unterschied.
Stimmt natürlich. Ich komme da immer mit den Nullables aus C# durcheinander :)

Warum macht man das überhaupt? Mit einem leeren Objekt kann man doch wenig anfangen oder?

Möglicherweise erwartet die Klasse, von der geerbt wird ein Objekt, welches !== undefined ist.
Außerdem kannst du so etwas code sparen. Wenn options undefined wäre, würdest du in Zeile 3 ein Problem bekommen. Denn der Compiler würde in etwa "Cannot read property options of undefined" ausspucken. Um das zu verhindern, bräuchte man dann etwas wie
Code:
options != null ? options['options'] : []
 
Zurück
Oben