なぜ私たちは super(props)
を書くの?
以下はWhy Do We Write super(props)? の日本語訳です。
(翻訳、おかしい部分があるかもしれませんが自己責任でお願いいたします。)
Hooks
が最新でアツいって聞いたよ。皮肉なことだけどクラスコンポーネントの楽しい事実について述べてブログをスタートしたい。どうだ!
これらの潜在的問題はReactを効率的に使うためには重要じゃない。でも、もしどうやって動いているか深く掘り下げることが好きなら面白いかもね
これが最初のやつ。
私は人生で super(props)
何度も書いたよ
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
もちろん、class fields proposal
なら儀式(constructor)をスキップできる。
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
2015年にReact 0.13
がプレーンクラスのサポートを追加したとき、こんな感じの構文が計画されていたよ。コンストラクタの定義とsuper(props)
の呼び出しは常にクラスフィールドが人間工学に基づいた代替手段を提供するまでの一時的な解決策だった。
でも、ES2015の機能のみを使って例に戻りましょう。
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
}
// ...
}
なぜ私たちはsuperを呼ぶの?呼ばなくてもいいの?もし呼ばないといけないなら、props
を呼ばなかったらなにが起こるんですか?他に何か議論はありますか?確認してみましょう。
JavaScriptではsuperは親クラスのコンストラクタを参照します。(この例では、親クラスはReact.Component実装を指しています。)
重要なのは、JavaScriptはあなたがコンストラクターで親のコンストラクターを呼ぶまでthis
は使わせてくれません。
class Checkbox extends React.Component {
constructor(props) {
// 🔴 `this` はまだ使えない
super(props);
// ✅ 今なら使える
this.state = { isOn: true };
}
// ...
}
あなたがthisを使う前にJavascriptが親のコンスラクターの実行を強制させるのには理由があります。クラス階層を考えてみてください
class Person {
constructor(name) {
this.name = name;
}
}
class PolitePerson extends Person {
constructor(name) {
this.greetColleagues(); // 🔴 ここでは許可されていない, 下記をご確認ください
super(name);
}
greetColleagues() {
alert('Good morning folks!');
}
}
super
の前にthis
の使用が許可されていた場合のことを想像してみてください。
一ヶ月後、greetColleagues
のメッセージに人の名前を入れるかもしれません。
greetColleagues() {
alert('Good morning folks!');
alert('My name is ' + this.name + ', nice to meet you!');
}
しかしthis.greetColleagues()
はsuper()
の前に呼ばれていることを忘れちゃいました。そう、this.name
はまだ定義されていません!
ご覧のとおり、このようなコードは考えるのが非常に難しいのです。
この落とし穴を避けるために Javascriptはコンストラクターでthisを使いたい場合にsuper
の呼び出しを強制します。
そして、この制限はクラス定義されたReactのコンポーネントにも適用されます。
constructor(props) {
super(props);
// ✅ ここから`this`が使える
this.state = { isOn: true };
}
他の疑問が残っています。なぜpropsを引数に渡すの?
React.Component
でコンストラクターがthis.props
を初期化するために、props
をsuper
に渡すことが必要と思うかもしれません。
// React内部
class Component {
constructor(props) {
this.props = props;
// ...
}
}
真実からそれほど遠くないですよ。確かにやっています
しかし、どういうわけか引数(props
)なしのsuper()
で呼び出しても、
this.props
にrender
や他のメソッド内でアクセスできます。(信じないなら試してみて!)
どうやって動いているんだ?
Reactもprops
をコンストラクターを呼んだ後にインスタンスに割り当てていることがわかる
// React内部
const instance = new YourComponent(props);
instance.props = props;
そう。だからもしprops
をsuper
に渡し忘れても、Reactはprops
を設定します。これには理由があります。
Reactがclassをサポートしたとき、ES6のクラスだけをサポートしたのではありません。
ゴールはより広いクラスの抽象概念をサポートすることでした。
コンポーネントを定義するのにClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScriptや他の方法がどれほど成功するのかは不明確でした。
だからES6のclassでsuper()
の呼び出しが必須であるにも関わらず、意図的に固執しませんでした。
これはsuper(props)
の代わりにsuper()
と書けるということを意味してる?
多分そうじゃない。まだ紛らわしい。
確かに、Reactはコンストラクターが実行されたあとにthis.props
を割り当てます。
でも、親とあなたのコンストラクターの実行が終わるまでの間、this.props
は未定義なのです。
// React内部
class Component {
constructor(props) {
this.props = props;
// ...
}
}
// あなたのコード
class Button extends React.Component {
constructor(props) {
super(); // 😬 props渡すの忘れちゃった
console.log(props); // ✅ {}
console.log(this.props); // 😬 undefined
}
// ...
}
コンストラクタから呼び出されるメソッドでこれが発生した場合、デバッグするのはさらに困難になります。
厳密に必要というわけではないですが、私は常にsuper(props)
で渡すことをオススメしています。
class Button extends React.Component {
constructor(props) {
super(props); // ✅ props渡した
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
// ...
}
これはコンストラクタが終了する前でもthis.props
は設定されているということを保証します。
長年のReactユーザーは興味があるかもしれないことを最後に少々。
Context APIをclass(古いタイプのcontextTypesもしくはReact 16.6で追加された新しいcontextTypeのどちらでも)内で使用する時、context
は2つ目の引数としてコンストラクターに渡されることに、気づいているかもしれません。
では、super(props, context)
と書いてみませんか?できますが、contextはそんなに頻繁に利用されないため、この落とし穴はそれほど頻繁に現れません。
class fields proposal
ではこの落とし穴はほとんど消えます。
明示的なコンストラクタがないと全ての引数は自動的に渡されます。
これはこのような式(state = {}
)に必要に応じてthis.props
もしくはthis.context
の参照を含めることを許します。
Hooksではsuper
もしくはthis
さえ持っていません。
しかし、これは別の日の話題としましょう。