Warum verwenden wir super(props)?
2018 M11 30 • ☕️ 5 min read
Translated by readers into: Deutsch • Español • Français • Italiano • Magyar • Nederlands • Norsk • Português do Brasil • Slovenčina • Tiếng Việt • Türkçe • srpski • Čeština • Українська • فارسی • ไทย • မြန်မာဘာသာ • 日本語 • 简体中文 • 繁體中文
Read the original • Improve this translation • View all translated posts
Hooks sind scheinbar der letzte Schrei. Ironischerweise möchte ich diesen Blogeintrag damit beginnen ein par Fun-Facts zum Thema class components loszuwerden. Wie wäre es damit!
Es ist absolut nicht notwendig diese Fakten zu kennen um effektiv mit React arbeiten zu können. Jedoch könnte es interessant werden, wenn man tiefer in die Materie einsteigen möchte.
Fun-Fact Nummer Eins.
Ich habe super(props)
bereits öfter schreiben dürfen, als es mir lieb ist:
class Checkbox extends React.Component {
constructor(props) {
super(props); this.state = { isOn: true };
}
// ...
}
Das class fields proposal macht es natürlich einfach das Ganze zu überspringen:
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
Eine ähnliche Syntax war bereits geplant, als 2015, mit React 0.13, die Unterstützung von plain classes hinzugefügt wurde. Das Definieren vom separaten Konstruktor
und der Aufruf von super(props)
war schon immer als Übergangslösung gedacht. Diese sollte letztlich durch eine ergonomischere Lösung mithilfe von Klassenattributen abgelöst werden.
Schauen wir uns das Beispiel nochmal an, jedoch mit den ES2015-Funktionalitäten:
class Checkbox extends React.Component {
constructor(props) {
super(props); this.state = { isOn: true };
}
// ...
}
Warum rufen wir super
auf? Können wir nicht darauf verzichten? Wenn kein Weg daran vorbei führt, was passiert wenn wir die props
nicht übergeben? Gibt es außerdem noch weitere Eingabeparameter? Gehen wir der Sache näher auf den Grund.
In Javascript weist super
immer auf den Konstruktor der Basisklasse. (In unserem Beispiel bezieht es sich auf die Implementation von React.Component
.)
Es sei wichtig zu wissen, dass man this
im Konstruktor erst nach dem Aufruf des eigentlichen Basiskonstruktors verwenden kann. Javascript erlaubt es nicht:
class Checkbox extends React.Component {
constructor(props) {
// 🔴 Kann `this` noch nicht verwenden
super(props);
// ✅ Jetzt funktioniert es
this.state = { isOn: true };
}
// ...
}
Es gibt einen guten Grund warum Javascript es verlangt den übergeordneten Konstruktor aufzurufen, bevor man this
nutzen darf. Stellen wir uns folgende Klassenhierarchie vor:
class Person {
constructor(name) {
this.name = name;
}
}
class PolitePerson extends Person {
constructor(name) {
this.greetColleagues(); // 🔴 Dies ist verboten, unten steht warum
super(name);
}
greetColleagues() {
alert('Guten Morgen, Freunde!');
}
}
Nehmen wir einmal an, es wäre erlaubt this
vor super
aufzurufen. Nach einem Monat ändern wir dann greetColleagues
, sodass es den Namen der Person mit beinhaltet:
greetColleagues() {
alert('Guten Morgen, Freunde!');
alert('Mein Name ist ' + this.name + ', freut mich euch kennenzulernen!');
}
An dieser Stelle vergessen wir jedoch, dass this.greetColleagues()
vor super()
aufgerufen wird. Das Attribut this.name
konnte also noch gar nicht richtig gesetzt werden. Zum Zeitpunkt an dem wir den Wert in der Methode abrufen ist this.name
noch nicht einmal definiert! Es ist wahrlich schwer sich mit solchem Code auseinanderzusetzen.
Um Fallen wie diesen aus dem Weg zu gehen wird von Javascript verlangt, dass super
aufgerufen werden muss, bevor man this
im Konstruktor benutzen darf. Lassen wir also die Superklasse ihr Ding machen! Diese Einschränkung trifft auch auf in Klassen definierte React-Komponenten zu:
constructor(props) {
super(props);
// ✅ Jetzt können wir `this` benutzen
this.state = { isOn: true };
}
Nun führt uns das alles jedoch zur nächsten Frage: Warum reichen wir die props
weiter?
Es ist denkbar, dass das Hinunterreichen der props
an super
notwendig ist, damit React.Component
seine this.props
korrekt setzen kann:
// Im Inneren von React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
Und dies ist gar nicht so falsch, letztendlich ist es sogar genau so.
Nun erscheint es jedoch eigenartig, dass, selbst wenn man super()
ohne jeglich Argumente aufruft, man in Methoden wie render
noch immer Zugriff auf this.props
hat. (Wenn Du mir nicht traust, probiere es selber aus!)
Wie kann das funktionieren? Es stellt sich heraus, dass React jeder Instanz ebenfalls props
zuweist, gleich nach Aufruf deines Konstruktors:
// Im Inneren von React
const instance = new YourComponent(props);
instance.props = props;
Somit sorgt React dafür, dass selbst wenn man vergisst die props
an super()
weiterzureichen, diese unmittelbar nach der Instanziierung automatisch gesetzt werden. Hierfür gibt es einen Grund.
Als in React die Unterstützung von Klassen hinzugefügt wurde, hat man dabei nicht nur ES6-Klassen berücksichtig. Das Ziel war es, ein möglichst weites Spektrum an Klassenabstraktionen abdecken zu können. Es war nicht absehbar, wie erfolgreich Lösungen hinsichtlich React-Komponenten mit ClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScript oder Anderen werden könnten. Aus diesem Grund blieb React absichtlich neutral gegenüber, ob ein Aufruf von super()
erforderlich sei - auch wenn dies für ES6-Klassen nötig ist.
Heißt das also, dass man einfach super()
anstelle von super(props)
schreiben kann?
Wahrscheinlich nicht, weil auch dies noch immer für Verwirrung sorgt. Einerseits würde React nach dem Aufruf des eigenen Konstruktors this.props
automatisch zuweisen, jedoch wären this.props
dann zwischen dem Aufruf von super
und dem Ende des Konstruktors nicht definiert:
// Im Inneren von React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
// Im eigenen Code
class Button extends React.Component {
constructor(props) {
super(); // 😬 Wir haben vergessen props zu übergeben
console.log(props); // ✅ {}
console.log(this.props); // 😬 nicht definiert }
// ...
}
Dieses Verhalten kann noch verwirrender werden, wenn es in einzelnen Methoden vorkommt, welche innerhalb eines Konstruktors aufgerufen werden. Ich empfehle daher, die props
immer mit super(props)
an die Basisklasse runterzureichen, selbst wenn dies nicht dringend notwendig ist:
class Button extends React.Component {
constructor(props) {
super(props); // ✅ Wir haben props übergeben
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
// ...
}
Damit stellt man sicher, dass this.props
schon vor Ende des Konstruktors gesetzt wurde.
Es gibt noch eine weitere Sache, die erfahrene React-Nutzer neugierig machen könnte.
Es könnte aufgefallen sein, dass beim Nutzen der Context-API in Klassen (entweder mittels der alten contextTypes
oder der modernen contextType
API, hinzugefügt in React 16.6), context
als zweites Argument an den Konstruktor übergeben wird.
Warum schreiben wir also nicht super(props, context)
? Dies wäre ohne Weiteres möglich, jedoch wird context seltener benutzt und daher stößt also seltener auf diese Falle.
Mit dem class fields proposal erübrigen sich mögliche Fallstricke allemal. Ohne einen expliziten Konstruktor werden alle Argumente automatisch reingereicht. Dadurch kann ein Ausdruck wie state = {}
bei Bedarf Verweise auf this.props
oder this.context
enthalten.
Mit Hooks braucht man nicht einmal mehr super
oder this
. Das ist allerdings ein Thema für einen anderen Tag.