さかなソフトブログ

プログラミングやソフトウェア開発に関する情報

プログラミング

[ES2015] classとメソッドによるイベントハンドラの書き方

投稿日:

Bind

Javascriptは関数型がベースにあり、Webで広く利用されるようになってクライアントサイドでもサーバーサイドでもよく利用されるようになりました。

その後、おそらくは関数型概念だけでは他の言語から入ってきた開発者等からは直感的ではない等の理由から、世の中の要望に応えるためにclass等のオブジェクト指向型言語で馴染みのある記述法をECMAScript 2015(ES6)より言語仕様として取り入れてきました。

しかしながら、実はJSのclassはシンタックスシュガーであり、ES2015以前から利用されていたfunctionによるクラス風構文の置き換えにすぎず、クラスもメソッドも依然としてfunctionである事は頭の片隅に置いておかないと怪奇現象に病まされることになりかねません。

そこで今回はclassメソッドをハンドラとして利用する場合にありがちな記述方法の間違いとオススメの書き方を今回の書き方に関係するthisとbindとarrow関数を簡単におさらいしながら理解を深めていきます。

なお、サンプルコードはReactNativeのコンポーネントでボタンが押されたときのイベントハンドラをメソッドとして記述したことを例に取ります。


スポンサーリンク

正方形336

ES2015以降オススメの書き方

理由とかどうでも良いから書き方教えてという方の為に、まずは結論を先に書いておきます。

class MyComponent extends Component {

  state = {
    isShow = false;
  }

  onPress = () => {
    this.setState({ isShow: true });
  }

  render() {
    return (
      <View>
        <TouchableOpacity onPress={this.onPress}>
          <Text>ボタン</Text>
        </TouchableOpacity>
        <Text>{this.state.isShow ? '押した' : '押してない'}</Text>
      </View>
    );
  }
}

クラスメソッドの定義としてarrow関数を使って記述すると良いというのがこの記事の提案です。

以下、どうしてそうなったのかを考察していきます。

やってはいけない書き方

以下はバグが混入しているサンプルです:

class MyComponent extends Component {

  state = {
    isShow = false;
  }

  onPress() {
    this.setState({ isShow: true });
  }

  render() {
    return (
      <View>
        <TouchableOpacity onPress={this.onPress}>
          <Text>ボタン</Text>
        </TouchableOpacity>
        <Text>{this.state.isShow ? '押した' : '押してない'}</Text>
      </View>
    );
  }
}

普通にonPress()でメソッド定義され、onPressに関数ハンドラそのメソッドthis.onPressを登録しています。

一見、なんの問題も無さそうに見えますが、setState()呼び出し時に例外が発生したり、this.state.isShow状態変化しなかったりします。

ここでJSのclassはシンタックスシュガーであることを思い出して下さい。this.onPressJSではインスタンスメソッドでは無くfunctionです。

onPressで呼びされたハンドラ内、つまり、onPress()で記述されたthis.setState()thisイベント呼び出し元の関数スコープにおけるthisとなってMyComponentのインスタンスではないということです。

正しく挙動する書き方案1

ここからはよく見かけるハンドラ記述案を見ていきます。先ずはbindを利用した方法です:

class MyComponent extends Component {

  state = {
    isShow = false;
  }

  onPress() {
    this.setState({ isShow: true });
  }

  render() {
    return (
      <View>
        <TouchableOpacity onPress={this.onPress.bind(this)}>
          <Text>ボタン</Text>
        </TouchableOpacity>
        <Text>{this.state.isShow ? '押した' : '押してない'}</Text>
      </View>
    );
  }
}

func.bind(obj)関数funcにobjを結びつけた関数を生成して返します。

サンプルコードのthis.onPress.bind(this)MyComponent生成時のインスタンスthisに結びつけた関数をonPressイベントハンドラとして登録しているので、イベント呼び出された先のthisMyComponentインスタンスのものとなり正しく動作します。

巷のドキュメントサンプルコード等でもこの記述方法が書かれていたりするのでこれでも問題はありません。

しかしながら、親コンポーネントからイベントハンドラ登録するときなどに子コンポーネントがbind(this)で記述されているかを確認しなければならない等デメリットがあります。

正しく挙動する書き方案2

次はアロー関数を使った記述案です。

class MyComponent extends Component {

  state = {
    isShow = false;
  }

  onPress() {
    this.setState({ isShow: true });
  }

  render() {
    return (
      <View>
        <TouchableOpacity onPress={() => { this.onPress(); }}>
          <Text>ボタン</Text>
        </TouchableOpacity>
        <Text>{this.state.isShow ? '押した' : '押してない'}</Text>
      </View>
    );
  }
}

アロー関数(Arrow function)はES2015(ES6)で追加された言語仕様で、無名関数(function () { ... })を置き換えることが出来ます。但し、無名関数と違って関数内のthisを置き換えないで定義側の関数スコープで動作します。

従って、アロー関数内で呼び出されたthis.onPress()thisMyComponentインスタンスで関数が呼び出されているのでonPress()メソッド内のthisMyComponentインスタンスとなって正しく動作します。

こちらの書き方も動作上問題ありませんが、呼び出し側が呼び出し元の記述方法を気にする必要があります。

正しく挙動する書き方案3(ES2015以降オススメ)

最初に書いた結論のコードをもう一度見てみます。

class MyComponent extends Component {

  state = {
    isShow = false;
  }

  onPress = () => {
    this.setState({ isShow: true });
  }

  render() {
    return (
      <View>
        <TouchableOpacity onPress={this.onPress}>
          <Text>ボタン</Text>
        </TouchableOpacity>
        <Text>{this.state.isShow ? '押した' : '押してない'}</Text>
      </View>
    );
  }
}

インスタンスメソッド側をアロー関数で定義してイベントハンドラを登録しています。

こうすることによって、イベントハンドラ呼び出し側がメソッド登録、bind付き登録、アロー関数呼び出し登録といったどのような書き方をしていても影響を受けずにMyComponentインスタンスでonPressメソッドが呼び出されて正しく動作します。

まとめ

いかがだったでしょうか。

このようなことを考えると個人的にはES2015でのclassメソッドのシンタックスシュガーは最初からアロー関数の置き換え、つまり、class内で利用するメソッドのthisはクラスインスタンスとすれば良かったのでは無いかと思いますが、JSは関数型言語なので譲れなかった部分もあるかもしれません。

なんにせよ、ES2015でクラスやアロー関数が記述出来る様になったのはとても便利なので、JS特有の性質も理解しながら上手く付き合って行きたいものですね 😉

正方形336

正方形336

-プログラミング
-,

Copyright© さかなソフトブログ , 2019 All Rights Reserved.