りんごとバナナとエンジニア

エンジニア修行の記録

ReactのSyntheticEventとは何なのか

Reactでイベントを作るとき、引数にeを渡す例がよくある。
例えば公式ドキュメントにもこんなコードが載ってる。

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

このeSyntheticEventを表すものらしいが、どんなものなのかいまいちつかめないのでいろいろ調べてみた。
特にこのページがわかりやすく、役に立った。

www.kirupa.com

通常のイベントとの違い

Reactを使わず、通常のJSでイベントを実装するときは、イベントの種類によって様々なインターフェイスがある。
例えばクリックイベントを作るならMouseEventを使い、キーボード入力をイベントリスナーとするならKeyBoardEventを使う。
イベントインターフェイスの種類によって、独自のプロパティが実装されており、ブラウザごとの対応状況も異なる。そのため、ネイティブのJSでイベントをクロスブラウザ対応させるのはなかなか大変らしい。

ReactのSynthetic Event

一方Reactの場合、全てのイベントをSyntheticEventとして扱う。
Synthetic Eventを使えば、各ブラウザのネイティブイベントを全てラップしており、何も意識しなくてもクロスブラウザ対応ができる。

そして冒頭に出てきたeは、イベントが発生した際にそれをSyntheticEventとしてイベントハンドラに渡す役割がある。
単なる引数にすぎないので名前は何でもいい。eventにしている人もいた。

SyntheticEventのプロパティ

SyntheticEventに用意されているプロパティは以下のような感じ。

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type

どのネイティブイベントにもあてはまりそうな、一般性の高いものが選ばれている。

ネイティブイベント特有のプロパティを使うには

個々のネイティブイベント特有のプロパティを使いたい場合も問題ない。
SyntheticEventが、イベントの名前によって何のイベントをラップしているか識別し、それに合わせてプロパティを使えるようになる。

例えば、公式が作ったコードを少し修正して、以下のようなコードを書いてみた。
もともとのコードは、ボタンをクリックするとオン・オフが切り替わるというだけのものだったが、
今回の修正により、shiftキーを押しながらクリックしないと切り替わらないようにしてみた。

See the Pen Toggle - ShiftKey ver. by Udomomo (@Udomomo) on CodePen.

修正したのは以下の部分。
handleClick関数に引数eを渡し、e.shiftKeyで条件分岐させてみた。

  handleClick(e) {   // eを追加
    if (e.shiftKey) {  // e.shiftKeyによる条件分岐を追加
      this.setState(prevState => ({
        isToggleOn: !prevState.isToggleOn
      }));
    }
  }

なぜこれが動くかというと、render関数の中でonClickという名前でイベントを定義しているため。
これにより、SyntheticEventが「自分がラップしているネイティブイベントはonMouseEventだ」と認識し、ネイティブJSのMouseEventオブジェクトのプロパティであるShiftKeyを使えるようになったのだ。

イベントの名前とプロパティの対応関係は、公式ドキュメントに載っている。

SyntheticEventのPoolingについて

公式ドキュメントには、「The SyntheticEvent is pooled」とある。
これは、SyntheticEventオブジェクト自体が再利用されるということ。
SyntheticEventのコールバック関数の実行が終わると、プロパティの値が全てnullにリセットされる。

すなわち、あるSyntheticEventの中でプロパティに値を設定した後、それを非同期で呼び出す別の処理で使ったり、値を取っておいて後で別の関数の中で使おうとしても、nullしか返ってこない。
event.persist()を使えば、非同期処理でもプロパティの値を参照できるようになるらしいが、Reactのパフォーマンスが落ちてしまうようだ。