(JSX) メソッド内でthisを正しく参照する


React.Componentのメソッドを使う際にthisが正しく参照できないことがあった。その原因と対処法を記す。動かせるコードはこちら

問題

Click Me!という文字列をクリックした時にThanksと文字列が切り替わるようなコンポーネントを実装したい。

ここで次のようなコードにすると、クリックしても文字が切り替わらない。

class Clickable extends React.Component {
  constructor(props) {
    super(props);
    this.state = { msg: "Click Me!"};
  };
  render () {
     return <h1 onClick={this.handleClick}>{ this.state.msg }</h1>;
  };
  handleClick () { this.setState({ msg: "Thanks!"}); };
}

ReactDOM.render(
  <Clickable/>,
  document.getElementById('mount')
);

原因はメソッドhandleClickを定義した時に、thisのスコープがメソッド内のものに置き換わってしまい、クラス内のスコープのthisを参照することが出来ないためだ。

正しく動作させるための対処法を以下に書いていく。

対処法①:コンストラクタで束縛する

最初の対処法はクラスのコンストラクタ内でクラスのスコープでのthisをメソッドに束縛させる方法だ。

class Clickable extends React.Component {
  constructor(props) {
    super(props);
    this.state = { msg: "Click Me!"};
    // 対処法①  コンストラクタ内でhandleClickを束縛する
    this.handleClick = this.handleClick.bind(this);
  };
  ...
}

この方法はうまくいくが、束縛はコンストラクタ内で行う必要があるため、コンストラクタを省略することが出来なくなるという欠点がある。

対処法②:クロージャを使う

2番目の対処法はメソッド呼び出しの時にクロージャを使うことだ。renderが実行された時、クロージャによってクラスのthisが束縛されるので、メソッド内で正しく参照を行うことが出来るようになる。

 render () {
     return <h1 onClick={() => this.handleClick()}>{ this.state.msg }</h1>;
  };

この書き方ではコンストラクタ内で束縛する必要がなくなるため、コンストラクタが不必要な場合は省略することが出来る。

対処方③:アロー関数を使う

最後の対処法はメソッドhandlwClickの定義にアロー関数表記を使うことだ。アロー関数は変数のリスコープを行わないので、元々のthisをメソッド内部でそのまま参照することが出来る。JavaScriptでは関数は第1級のオブジェクトであるため、アロー関数をクラスのメンバ変数として登録している形となっている。

handleClick = () =>  { this.setState({ msg: "Thanks!"}); };

この書き方でもコンストラクタ内で束縛を行う必要はないので、コードが短くなる。

まとめ

メソッド内でクラスのthisを参照する方法を3つ紹介した。
どうもJSのスコープは奥が深いというかクセがある。。