Dlaczego piszemy super(props)?
2018 M11 30 • ☕️ 5 min read
Translated by readers into: Deutsch • Español • Français • Italiano • Magyar • Nederlands • Norsk • Polski • 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
Słyszałem, że Hooks są teraz na fali. Na przekór temu zacznę od ciekawostek dotyczących komponentów pisanych jako klasy. Co ty na to!
Te haczyki nie są istotne, żeby wydajnie używać React. Może wydadzą ci się zabawne jeśli lubisz zgłębiać jak coś dokładnie działa.
Teraz pierwszy z nich.
W swoim życiu napisałem super(props)
więcej razy niż chciałbym wiedzieć:
class Checkbox extends React.Component {
constructor(props) {
super(props); this.state = { isOn: true };
}
// ...
}
Oczywiście propozycja pól w klasach pozwala skrócić tę ceremonię:
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
Taka składnia była planowana kiedy React 0.13 dodał wsparcie dla prostych klas w roku 2015. Definiowanie funkcji constructor
i wołanie super(props)
było zawsze uważane jako rozwiązanie tymczasowe aż do momentu kiedy pola w klasach staną się wygodną alternatywą.
Wróćmy do naszego przykładu wykorzystującego tylko składnię ES2015:
class Checkbox extends React.Component {
constructor(props) {
super(props); this.state = { isOn: true };
}
// ...
}
Dlaczego wołamy super
? Czy możemy go nie wywoływać? A jeżeli już musimy to co stanie się jeśli nie przekażemy props
? Czy są jakieś inne argumenty? Sprawdźmy to.
W JavaScript super
odnośi się do konstruktora klasy nadrzędnej. (W naszym przykładzie wskazuje na implementację React.Component
.)
Co istotne w konstruktorze nie można używać this
dopóki nie wywołamy konstruktora nadrzędnego. JavaScript nie pozwoli na coś takiego:
class Checkbox extends React.Component {
constructor(props) {
// 🔴 Jeszcze nie możesz używać `this`
super(props);
// ✅ Teraz już można
this.state = { isOn: true };
}
// ...
}
Istnieje dobry powód dlaczego JavaScript wymaga wywołania konstruktora nadrzędnego zanim będzie można użyć słowa kluczowego this
. Wyobraźmy sobie taką hierarchię klas:
class Person {
constructor(name) {
this.name = name;
}
}
class PolitePerson extends Person {
constructor(name) {
this.greetColleagues(); // 🔴 Tak nie wolno, przeczytaj poniżej dlaczego
super(name);
}
greetColleagues() {
alert('Witajcie przyjaciele!');
}
}
Wyobraź sobie, że użycie this
zanim wywołamy super
byłoby dozwolone. Miesiąc później moglibyśmy zmienić greetColleagues
tak, żeby zawierało imię osoby w wiadomości na powitanie:
greetColleagues() {
alert('Witajcie przyjaciele!');
alert('Nazywam się ' + this.name + ', miło cię poznać!');
}
Zapomnieliśmy jednak, że this.greetColleagues()
jest wywoływane zanim super()
miało okazję ustawić this.name
. Tak więc this.name
nie jest jeszcze nawet zdefiniowane! Jak widzisz taki kod może być trudny do zrozumienia.
Żeby uniknąć takich pułapek, JavaScript wymaga, że przed użyciem this
w konstruktorze, najpierw trzeba wywołać super
. Niech rodzić zrobi co trzeba! To samo ograniczenie ma zastosowanie w komponentach React definiowanych jako klasy:
constructor(props) {
super(props);
// ✅ Teraż można używać `this`
this.state = { isOn: true };
}
Zostało nam jeszcze jedno pytanie: dlaczego przekazujemy props
?
Może wydawać ci się, że przekazywanie props
do super
jest konieczne, żeby bazowy konstruktor React.Component
mógł inicjalizować this.props
:
// Wewnątrz React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
Nie jest to dalekie od prawdy — rzeczywiście, tak właśnie robi.
Jednak nawet jeżeli wywołasz super()
bez przekazania argumentu props
, nadal będziesz miał dostęp do this.props
w metodzie render
i w pozostałych metodach. (Sprawdź to jeżeli mi nie wierzysz!)
W jaki sposób to działa? Okazuje się, że React również przypisuje props
do instancji klasy zaraz po wywołaniu twojego konstruktora:
// Wewnątrz React
const instance = new YourComponent(props);
instance.props = props;
Tak więc nawet jeżeli zapomnisz przekazać props
do super()
, React ustawi je za ciebie chwilę później. Dzieje się to nie bez powodu.
Kiedy React dodał wsparcie dla klas, nie było to tylko wsparcie klas ES6. Wtedy nie było jeszcze jasne jak duży sukces odniosą ClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScript i inne sposoby definiowania komponentów. Dlatego React celowo nie określał czy wywołanie super()
ma być wymagane — pomimo tego, że jest tak w klasach ES6.
Czy w takim razie możesz używać samego super()
zamiast super(props)
?
Prawdopodobnie nie ponieważ jest to mylące. Oczywiście React za chwilę przypisze this.props
po wywołaniu twojego konstruktora. Jednak this.props
byłoby undefined
pomiędzy wywołaniem super
aż do końca twojego konstruktora:
// Wewnątrz React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
// W twoim kodzie
class Button extends React.Component {
constructor(props) {
super(); // 😬 Zapomnieliśmy przekazać props
console.log(props); // ✅ {}
console.log(this.props); // 😬 undefined }
// ...
}
Jeszcze większym wyzwaniem będzie debugowanie jeżeli dzieje się tak wewnątrz metody wywoływanej w konstruktorze. Dlatego zalecam przekazywanie super(props)
zawsze, niezależnie od tego czy jest to konieczne w danym momencie:
class Button extends React.Component {
constructor(props) {
super(props); // ✅ Przekazujemy props
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
// ...
}
Dzięki temu this.props
jest ustawione przed zakończeniem wykonywania się konstruktora.
Jest jeszcze jedna ciekawostka dla tych, którzy używają React’a od dawna.
Być może wpadło ci w oko, że kiedy używasz Context API w klasach (niezależnie od tego czy starego contextTypes
czy nowego API contextType
dodanego w React 16.6), context
przekazywany jest jako drugi argument konstruktora.
Dlaczego więc nie piszemy super(props, context)
? Moglibyśmy, ale ponieważ context używany jest rzadziej to w konsekwencji problem ten nie pojawia się równie często.
Wraz z pojawieniem się propozycji pól w klasach cały ten problem w zasadzie przestaje mieć znaczenie. Kiedy brak zdefiniowanego konstruktora wszystkie argumenty są przekazywane automatycznie. Właśnie dzięki temu wyrażenie takie jak state = {}
może zawierać odniesienia do this.props
lub this.context
jeżeli jest to konieczne.
A kiedy zaczynamy używać Hooks nie mamy nawet do czynienia z super
ani z this
, ale to już temat na inny dzień.