Overreacted

Miért írjuk mindig, hogy super(props)?

2018 M11 30 • ☕️ 5 min read

Tudom, hogy mostanában a Hookok vannak középpontban, éppen ezért irónikus, hogy class komponensekkel kapcsolatos tényekkel kezdem el ezt a blogot. És akkor mi van?!

Ezek a ‘jaj, már értem!’ pillanatok nem fontosak a React használatát illletően, de mindenképpen szórakoztatóak, ha szeretsz elmerülni a dolgok működésében.

Íme az első.


Többször írtam már le a super(props)-ot életemben, mint, hogy azt tudni akarnád:

class Checkbox extends React.Component {
  constructor(props) {
    super(props);    this.state = { isOn: true };
  }
  // ...
}

Természetesen a class-okat érintő, tervezett újítások megspórolják nekünk a vesződést:

class Checkbox extends React.Component {
  state = { isOn: true };
  // ...
}

Az ilyen típusú szintaxist akkor tervezték, amikor a React 0.13 megkapta a class-ok támogatását, még 2015-ben. A konstruktorok definiálása és a super(props) hívása mindig is ideiglenes megoldásnak számított, egészen addig, amíg a class alapú mezők egy kényelmes alternatívát nem nyújtottak.

Térjünk vissza ugyanerre a példára, csak már ES2015-ös újdonságokkal:

class Checkbox extends React.Component {
  constructor(props) {
    super(props);    this.state = { isOn: true };
  }
  // ...
}

Miért hívjuk meg a super föggvényt? Megtehetjük, hogy nem hívjuk meg? Ha muszáj meghívnunk, akkor mi történik, amikor nem adunk át neki props értéket? Létezik más argumentuma? Derítsük ki!


JavaScript-ben a super függvény a szülő osztály konstruktorára utal. (A példánkban, a React.Component-ben találhatóra mutat.)

Még fontosabb tudni, hogy nem használhatjuk a this hivatkozást egy konstruktorban,amíg a szülő konstruktorának hívása meg nem történt. A JavaScript egyszerűen nem fogja engedni:

class Checkbox extends React.Component {
  constructor(props) {
    // 🔴 Can’t use `this` yet
    super(props);
    // ✅ Now it’s okay though
    this.state = { isOn: true };
  }
  // ...
}

Jó oka van a JavaScript-nek, hogy ha erőszakkal is, de lefuttatja velünk a szülő osztály konstruktorát mielőtt hozzáférünk a this hivatkozáshoz. Gondoljuk csak végig a class felépítését:

class Person {
  constructor(name) {
    this.name = name;
  }
}

class PolitePerson extends Person {
  constructor(name) {
    this.greetColleagues(); // 🔴 This is disallowed, read below why
    super(name);
  }
  greetColleagues() {
    alert('Good morning folks!');
  }
}

Képzeljük el, hogy elkezdjük használni a this-t mielőtt meghívnánk a super függvényt. Egy hónappal később mondjuk megváltoztatjuk a greetColleagues függvényt, hogy az általa kiírt üzenet tartalmazza a személy nevét:

  greetColleagues() {
    alert('Good morning folks!');
    alert('My name is ' + this.name + ', nice to meet you!');
  }

De tegyük fel, hogy elfelejtjük, hogy a this.greetColleagues() függvény előbb meghívódik, mielőtt a super() függvény be tudná állítani a this.name változót. Ebben az esetben a this.name még csak nem is lett definiálva. Ahogyan azt láthatjuk is, nagyon nehéz ilyen típusú kóddal tervezni és gondolkodni.

Hogy elkerüljük az ilyen buktatókat, a JavaScript kényszerít minket, hogy ha a this hivatkozást szeretnénk használni, akkor előtte előszőr a super metódust kell meghívnunk. Engedjük a szülőket, hogy tegyék a dolgukat! Ez a korlátozás a React komponensek meghatározására is érvényes:

  constructor(props) {
    super(props);
    // ✅ Okay to use `this` now
    this.state = { isOn: true };
  }

Egy kérdés maradt hátra: miért kell átadnunk a props argumentumot?


Azt hihetnéd, hogy azért szükséges, mert props átadásával a super függvény beállítja a this.props kezdeti értékét a React.Component-en belül

// Inside React
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}

Ez nincs is messze az igazságtól - valójában ezt csinálja.

De, ha meghívjuk a super() függvényt a props argumentum nélkül, akkor is el fogjuk tudni érni a this.props-ot a render és az összes többi függvényen belül. (Ha nem hiszel nekem, próbáld ki!)

Hogyan lehetséges ez? Ebből kiderül, hogy a React hozzárendeli a props argumentumot a példányunkhoz, rögtön miután meghívta az adott példány *konstruktorát::

  // Inside React
  const instance = new YourComponent(props);
  instance.props = props;

Tehát, még ha el is felejted a props-ot átadni a super() függvénynek, a React ezt megteszi helyetted. Ennek oka van.

Amikor a React megkapta a class-ok támogatását, akkor nem csak az ES6 osztályok tulajdonságát kapta meg. A cél az volt, hogy olyan széles körben fedjük le az egyes osztályok absztakciókat, amennyire csak lehetséges. Nem teljesen volt világos, hogy mennyire lesz sikeres a ClojureScript-tel, CoffeeScript-tel, ES6-tal, Fable-el, Scala.js-el, TypeScript-tel, vagy más komponens alapú megoldással. Míg az ES6-ot igen, addig a React-ot szándékosan tervezték úgy, hogy ne függjön attól, hogy a super() függvény meg lett-e hívva vagy sem.

Ez azt jelenti, hogy elég mostantól super() függvényt hívni a super(props) helyett?

Természetesen nem, mert zavaró. Persze, a React gondoskodna a this.props későbbi hozzárendeléséről, miután a konstruktor meghívódott, de a this.props még mindig undefined lenne a super függvény meghívása és a konstruktor vége között:

// Inside React
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}

// Inside your code
class Button extends React.Component {
  constructor(props) {
    super(); // 😬 We forgot to pass props
    console.log(props);      // ✅ {}
    console.log(this.props); // 😬 undefined   }
  // ...
}

Sokkal nagyobb kihívást jelenthet a hibakeresés is, ha ez valamelyik metódusban történik, amely a konstruktorból lett meghívva. Ezért javaslom, hogy mindig adjuk át a super() függvénynek a props-t, még ha ez nem is szigorúan kötelező:

class Button extends React.Component {
  constructor(props) {
    super(props); // ✅ We passed props
    console.log(props);      // ✅ {}
    console.log(this.props); // ✅ {}
  }
  // ...
}

Ezzel biztosak lehetünk abban, hogy a this.props beállításra kerül, még a konstruktor lefutása előtt.


Van még egy dolog, amire biztosan kíváncsiak a React felhasználók.

Biztosan megfigyelted már, hogy, amikor a Context API-t használod class-okon belül (még a régi contextTypes és az új contextType (React 16.6) esetében is), a context, mint második paraméter érkezik a konstruktorba.

Miért is nem írjuk egyszerűen, hogy super(props, context)? Megtehetnénk, de a context nagyon ritkán kerül felhasználásra, ezért ez a szituáció nem merül fel túl gyakran.

A class-ok mezőit érintő újításoknak köszönhetően ezek a buktatók teljesen megszűnnek. Létező konstuktor nélkül is minden argumentum automatikusan átadásra kerül a komponensek számára. Ennek köszönhetően használhatjuk a state = {} kifejezést, hogy hivatkozásokat ágyazhassunk be a this.props-ot és a this.context-et illetően, persze csak ha szükséges.

A Hook-ok esetében egyáltalán nincs super függvényünk vagy this hivatkozásunk, de ez már egy másik történet, másik alkalomra.