はじめに
普段、というかよくVueでアプリ開発をしていて、ある時Reactで開発をすることになった時、おそらくVueユーザーは最初、Vueと同じようなノリで
onClick={handleClick(hoge)}
とやった時、なぜかクリック時に関数が発火しないことに気づくと思います。
そこで、今回はVueユーザー向けにReactやる時は微妙にイベントハンドラの仕様が違うんだよ!という内容を説明していきます。
結論
結論を先に書いてしまうと、Vueのテンプレートでの書き方とReactで使うようなJSXのイベントハンドラは微妙に仕様が異なっており、イベントハンドラで呼び出す関数の書き方に注意する必要があります。
仕様 | |
---|---|
Vue | イベントハンドラに関数をそのまま指定する。 |
React | イベントハンドラに関数を実行する関数を指定する。 |
Vue
<button @click="handleClick(hoge)">ボタン</button>
const handleClick = (hoge) => {
console.log(hoge);
};
React
パターン1
<button onClick={() => handleClick(hoge)}>ボタン</button>
const handleClick = (hoge) => {
console.log(hoge);
};
パターン2 (カリー化)
使い方をVueに寄せるなら
<button onClick={handleClick(hoge)}>ボタン</button>
const handleClick = (hoge) => () => {
console.log(hoge);
};
カリー化についてはこちらを参照ください〜
まとめ
というように、イベントハンドラを使ってメソッドを呼び出す際には、Reactの時はVueとは若干違うことに注意してください。
ちなみにメソッドを呼び出す時に引数がいらなければReactもVueと同じように動作します。
<button onClick={handleClick}>ボタン</button>
const handleClick = () => {
console.log('Hello world!!');
};
デモ
Vue, Reactそれぞれで同じ処理を実装したデモは以下です。
ドリンクのボタンをクリックすると、クリックしたドリンク名が下に表示されます。
VueのコードはVueContainer.vue
に、ReactのコードはReactContainer.jsx
を参照ください。
それぞれの細かい仕様
Vue
ドキュメントには、
v-on ディレクティブを使用することで、 DOM イベントの購読やイベント発火時にいくつかの JavaScript を実行します。
とあります。
またVueにはインラインハンドラーとメソッドハンドラーの2種類のイベントハンドリング形式があります。
<!-- インラインハンドラー(単純なJavaScriptを実行するケース) -->
<button @click="count++">ボタン</button>
<!-- メソッドハンドラー(インラインでは複雑になるケース) -->
<button @click="handleClick">ボタン</button>
イベントハンドラーに渡された値がインラインハンドラーかメソッドハンドラーかはVue内部のテンプレートコンパイラがよしなに判断してくれるため、書き方によってのJavaScriptの実行タイミングの差分はありません。
また、メソッドにカスタムの引数を必要とする以下のようなケースはインラインハンドラーとして扱われます。
<!-- インラインハンドラーとして扱われる -->
<button @click="handleClick(hoge)">ボタン</button>
メソッドハンドラーにせよ、インラインハンドラーにせよ、いずれもイベント発火時にJavaScriptを実行します。
React
ReactもVueと同様で、シンプルな関数はpropsの一部としてイベントに渡すことができます。
<button onClick={handleClick}>ボタン</button>
しかし、以下のようなケース、Vueでいうインラインハンドラーのような時は注意が必要です。
<button onClick={handleClick(hoge)}>ボタン</button>
ドキュメントの「落とし穴」の箇所に記載があります。
イベントハンドラに渡す関数は、渡すべきなのであって、呼び出すべきではありません。
JSXでは、{}
内に記述されたJavaScriptはレンダリング後すぐに実行されてしまう(関数呼び出し扱いになる)仕組みのため、Vueと同じノリで関数を書いてしまうと、イベント発火時の処理は行われません。
<button onClick={handleClick(hoge)}>ボタン</button>
そのため、Reactでは関数を渡す関数を書いてあげることによって、onClickに処理を渡してあげます。
<button onClick={() => handleClick(hoge)}>ボタン</button>
VueとReact(JSX)の仕様を踏まえて
<button @click="handleClick(hoge)">ボタン</button>
<button onClick={handleClick(hoge)}>ボタン</button>
Reactにおいて、{}
内のJavaScriptがレンダリング後すぐに実行されるのはJSXの仕様によるものです。
ではなぜVueでは即座実行されず、Reactではレンダリング後すぐに実行されるのかというと、
Vueでは記述したVue専用のコード(@click=""
のような書き方など)に対してVueのテンプレートコンパイラがこれらを普通のJavaScriptにコンパイルする際、@click="handleClick(hoge)"
のような式を自動的に適切な関数呼び出しに変換します。(すなわち処理の実行タイミングはコンパイラ内で決定される)
しかし、Reactで使うJSXはJavaScriptの拡張構文的な役割で、JavaScriptの中にHTMLのようなコードを書くことができるようになったあくまでJavaScriptです。
そしてそのJSX内のHTMLコードの内部でJavaScriptを実行する方法が{}
でくくるという構文なので、{}
内部は普通のJavaScriptエリアだということを抑えておくと良いかもしれませんね。
それを踏まえると、
<button onClick={handleClick(hoge)}>ボタン</button>
がレンダリング後すぐにhandleClick(hoge)
が実行されてしまうのも納得ですね!
でもVueっぽくシンプルに書きたい!というあなたに
Reactでは通常、カスタムの引数を必要とする場合などは()=>{}
というように、無名関数でラップする必要がありますが、
<button onClick={() => handleClick(hoge)}>ボタン</button>
VueユーザーのあなたはVueと同じようにそんなものでラップせずにシンプルに書きたいと思う人もいるかもしれません。
<button onClick={handleClick(hoge)}>ボタン</button>
その場合、メソッド側の書き方を少し変えることで、上記のようなVueと同じような書き方でイベントをハンドリングすることができます。
const handleClick = (hoge) => () => {
console.log(hoge);
};
この() => () => {}
というアロー関数の書き方はカリー化という書き方です。
カリー化については以下を参照ください!
カリー化した書き方をすることで、無名関数でラップする以下のやり方と同じ処理を実装することができます。
<button onClick={() => handleClick(hoge)}>ボタン</button>
その場合、html部分は以下に書き直します。
<button onClick={handleClick(hoge)}>ボタン</button>
おわりに
私は普段Vueを使うことが多いのですが、過去にReactを使うときに「あれ、動かない。。。」ってなった経験があり、多分Vueユーザーが最初React(JSX)を触ると大体こうなるのでは?と思って書いてみました。
また今回、VueとReactの仕様を同じ環境で比較するんだったら、と考えた時にAstro使えるじゃん!!って思ったのでデモはAstroで構築しました。🚀
本題から逸れますが、こういうフレームワーク間の比較実験をするときにAstro環境は便利だなと思いました笑