Overreacted

なぜsuper(props) を書くの?

2018 M11 30 • ☕️ 3 min read

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を初期化するために、propssuperに渡すことが必要と思うかもしれません。:

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

真実からそれほど遠くないですよ。確かにやっています

しかし、どういうわけか引数(props)なしのsuper()で呼び出しても、 this.propsrenderや他のメソッド内でアクセスできます。(信じないなら試してみて!)

どうやって動いているんだ?Reactもpropsをコンストラクターを呼んだ後にインスタンスに割り当てていることがわかる :

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

そう。だからもしpropssuper()に渡し忘れても、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さえ持っていません。 しかし、これは別の日の話題としましょう。