イベントへの応答
Reactでは、JSXにイベントハンドラーを追加できます。イベントハンドラーは、クリック、ホバー、フォーム入力へのフォーカスなどのインタラクションに応答してトリガーされる独自の関数です。
学習内容
- イベントハンドラーのさまざまな記述方法
- 親コンポーネントからイベント処理ロジックを渡す方法
- イベントの伝播方法とその停止方法
イベントハンドラーの追加
イベントハンドラーを追加するには、まず関数を定義し、それを適切なJSXタグにプロップとして渡します。以下は、まだ何もしないボタンの例です:
ユーザーがクリックしたときにメッセージを表示するには、次の3つの手順に従います:
- あなたの
Buttonコンポーネントの内部で、handleClickという関数を宣言します。 - その関数内にロジックを実装します(メッセージ表示には
alertを使用します)。 - JSXの
<button>にonClick={handleClick}を追加します。
あなたはhandleClick関数を定義し、それをにプロップとして渡しました。handleClickはイベントハンドラーです。イベントハンドラー関数は:
- 通常、コンポーネントの内部で定義されます。
- 名前は
handleで始まり、その後にイベント名が続きます。
慣例として、イベントハンドラーはイベント名の前にhandleを付けて命名することが一般的です。onClick={handleClick}やonMouseEnter={handleMouseEnter}などをよく目にするでしょう。
あるいは、JSX内でインラインでイベントハンドラーを定義することもできます:
または、より簡潔にアロー関数を使用して:
これらのスタイルはすべて同等です。インラインイベントハンドラーは短い関数に便利です。
落とし穴
イベントハンドラーに渡す関数は、呼び出すのではなく、渡す必要があります。例:
| 関数を渡す(正しい) | 関数を呼び出す(間違い) |
|---|---|
<button onClick={handleClick}> | <button onClick={handleClick()}> |
違いは微妙です。最初の例では、handleClick関数がonClickイベントハンドラーとして渡されます。これはReactにその関数を記憶させ、ユーザーがボタンをクリックしたときだけあなたの関数を呼び出すように指示します。
2番目の例では、handleClick()の末尾の()が、レンダリング中にクリックなしで関数を実行します。これは、JSXの{と}の中のJavaScriptがすぐに実行されるためです。
インラインでコードを書く場合、同じ落とし穴が別の形で現れます:
| 関数を渡す(正しい) | 関数を呼び出す(間違い) |
|---|---|
<button onClick={() => alert('...')}> | <button onClick={alert('...')}> |
このようなインラインコードを渡しても、クリック時に発火せず、コンポーネントがレンダリングされるたびに発火します:
イベントハンドラーをインラインで定義したい場合は、次のように無名関数でラップします:
これにより、レンダリングごとに内部のコードを実行するのではなく、後で呼び出される関数が作成されます。
どちらの場合も、渡したいのは関数です:
<button onClick={handleClick}>はhandleClick関数を渡します。<button onClick={() => alert('...')}>は() => alert('...')関数を渡します。
イベントハンドラー内でのプロップスの読み取り
イベントハンドラーはコンポーネント内で宣言されるため、コンポーネントのプロップスにアクセスできます。以下は、クリック時にそのmessageプロップスを含むアラ
これにより、2つのボタンが異なるメッセージを表示できます。渡すメッセージを変更してみてください。
イベントハンドラをpropsとして渡す
多くの場合、親コンポーネントが子コンポーネントのイベントハンドラを指定したいと思うでしょう。ボタンを考えてみてください:Buttonコンポーネントを使用する場所によって、実行する関数は異なるかもしれません。例えば、1つは映画を再生し、もう1つは画像をアップロードするかもしれません。
これを行うには、コンポーネントが親から受け取るpropをイベントハンドラとして次のように渡します:
ここでは、ToolbarコンポーネントがPlayButtonとUploadButtonをレンダリングしています:
PlayButtonはhandlePlayClickを内部のButtonのonClickpropとして渡します。UploadButtonは() => alert('Uploading!')を内部のButtonのonClickpropとして渡します。
最後に、あなたのButtonコンポーネントはonClickというpropを受け取ります。それはそのpropを組み込みのブラウザの<button>にonClick={onClick}で直接渡します。これはReactにクリック時に渡された関数を呼び出すように指示します。
もしデザインシステムを使用する場合、ボタンのようなコンポーネントがスタイルを含むが動作を指定しないことは一般的です。代わりに、PlayButtonやUploadButtonのようなコンポーネントがイベントハンドラを渡します。
イベントハンドラpropsの命名
組み込みコンポーネントである<button>や<div>は、ブラウザのイベント名であるonClickのようなもののみをサポートしています。しかし、独自のコンポーネントを構築する場合、そのイベントハンドラpropsには任意の名前を付けることができます。
慣例として、イベントハンドラpropsはonで始まり、その後ろに大文字が続きます。
例えば、ButtonコンポーネントのonClickpropはonSmashと呼ばれても構いません:
この例では、<button onClick={onSmash}>は、ブラウザの<button>(小文字)がonClickというpropを必要とすることを示していますが、あなたのカスタムButtonコンポーネントが受け取るpropの名前はあなた次第です!
コンポーネントが複数のインタラクションをサポートする場合、アプリ固有の概念に基づいてイベントハンドラpropsを命名することがあります。例えば、このToolbarコンポーネントはonPlayMovieとonUploadImageというイベントハンドラを受け取ります:
ここで、Appコンポーネントが、どのようにToolbarがonPlayMovieやonUploadImageをToolbarこれはToolbarの実装の詳細です。ここでは、onClickがそれらをButtonハンドラーとして自身のonPlayMovieに渡していますが、後でキーボードショートカットでトリガーすることもできます。のようなアプリ固有のインタラクションに基づいてプロップを命名することで、後でそれらがどのように使用されるかを柔軟に変更できます。
注意
イベントハンドラーには適切なHTMLタグを使用してください。例えば、クリックを処理するには、<button onClick={handleClick}>を使用し、<div onClick={handleClick}>は使用しないでください。実際のブラウザの<button>を使用すると、キーボードナビゲーションなどの組み込みのブラウザ動作が有効になります。ボタンのデフォルトのブラウザスタイルが気に入らず、リンクや他のUI要素のように見せたい場合は、CSSで実現できます。アクセシブルなマークアップの書き方について詳しく学ぶ。
イベントの伝播
イベントハンドラーは、コンポーネントが持つ可能性のある子要素からのイベントもキャッチします。イベントがツリーを「バブル」または「伝播」すると言います。つまり、イベントが発生した場所から始まり、ツリーを上っていきます。
この<div>には2つのボタンが含まれています。<div>両方と各ボタンはそれぞれ独自のonClickハンドラーを持っています。ボタンをクリックしたとき、どのハンドラーが発火すると思いますか?
どちらかのボタンをクリックすると、まずそのボタンのonClickが実行され、次に親の<div>のonClickが実行されます。したがって、2つのメッセージが表示されます。ツールバー自体をクリックすると、親の<div>のonClickのみが実行されます。
落とし穴
Reactでは、onScrollonScrollを除くすべてのイベントが伝播します。は、それをアタッチしたJSXタグでのみ機能します。
伝播の停止
イベントハンドラーは唯一の引数としてイベントオブジェクトを受け取ります。慣例として、通常は「event」を意味するeと呼ばれます。このオブジェクトを使用して、イベントに関する情報を読み取ることができます。
そのイベントオブジェクトは、伝播を停止することもできます。イベントが親コンポーネントに到達するのを防ぎたい場合は、次のe.stopPropagation()を呼び出す必要があります:Buttonコンポーネントのように
ボタンをクリックすると:
- Reactは、
onClickに渡された<button>ハンドラを呼び出します。 - そのハンドラ(
Buttonコンポーネントで定義されたもの)は、以下の処理を行います:- イベントの伝播をそれ以上進めないようにするために、
e.stopPropagation()を呼び出します。 - 親コンポーネントである
onClickから渡されたpropsであるToolbar関数を呼び出します。
- イベントの伝播をそれ以上進めないようにするために、
- その関数(
Toolbarコンポーネントで定義されたもの)は、ボタン自身のアラートを表示します。 - 伝播が停止されたため、親の
<div>のonClickハンドラは実行されません。
結果として、e.stopPropagation()により、ボタンをクリックしても、2つのアラート(<button>と親のツールバーの<button>からのもの)ではなく、1つのアラート(<div>からのもの)のみが表示されます。ボタンをクリックすることは、周囲のツールバーをクリックすることとは異なるため、このUIでは伝播を停止することが理にかなっています。
伝播の代替としてのハンドラの受け渡し
このクリックハンドラが、コードの1行を実行してから、親から渡されたonClickpropsを呼び出すことに注目してください:
親のonClickイベントハンドラを呼び出す前に、このハンドラにさらにコードを追加することもできます。このパターンは、伝播の代替手段を提供します。子コンポーネントがイベントを処理しつつ、親コンポーネントが追加の動作を指定できるようにします。伝播とは異なり、自動的ではありません。しかし、このパターンの利点は、あるイベントの結果として実行されるコードの連鎖全体を明確に追跡できることです。
伝播に依存していて、どのハンドラが実行されるのか、なぜ実行されるのかを追跡するのが難しい場合は、代わりにこのアプローチを試してください。
デフォルトの動作の防止
一部のブラウザイベントには、それに関連付けられたデフォルトの動作があります。例えば、<form>のsubmitイベント(内部のボタンがクリックされたときに発生する)は、デフォルトでページ全体を再読み込みします:
イベントオブジェクトに対してe.preventDefault()を呼び出すことで、これを防ぐことができます:
混同しないでください。e.stopPropagation()とe.preventDefault()はどちらも有用ですが、互いに関連していません:
- e.stopPropagation()は、上位のタグにアタッチされたイベントハンドラの発動を停止します。
- e.preventDefault()は、デフォルトのブラウザの動作を持つ一部のイベントに対して、その動作を防ぎます。
イベントハンドラは副作用を持てますか?
もちろんです!イベントハンドラは副作用を扱うのに最適な場所です。
レンダリング関数とは異なり、イベントハンドラは純粋である必要はないため、何かを変更するのに最適な場所です。例えば、タイピングに応じて入力値を変更したり、ボタン押下に応じてリストを変更したりします。ただし、情報を変更するには、まずそれを保存する方法が必要です。Reactでは、これはstate(コンポーネントのメモリ)を使用して行われます。次のページでそのすべてを学びます。
まとめ
- イベントは、
<button>のような要素に関数をpropsとして渡すことで処理できます。 - イベントハンドラは、呼び出すのではなく、渡す必要があります!
onClick={handleClick}であって、onClick={handleClick()}ではありません。 - イベントハンドラ関数は、別途定義することも、インラインで定義することもできます。
- イベントハンドラはコンポーネント内で定義されるため、propsにアクセスできます。
- 親コンポーネントでイベントハンドラを宣言し、それを子コンポーネントにpropsとして渡すことができます。
- アプリケーション固有の名前で独自のイベントハンドラpropsを定義できます。
- イベントは上方に伝播します。それを防ぐには、第一引数で
e.stopPropagation()を呼び出します。 - イベントには望ましくないデフォルトのブラウザ動作がある場合があります。それを防ぐには、
e.preventDefault()を呼び出します。 - 子コンポーネントのハンドラから親のイベントハンドラpropを明示的に呼び出すことは、伝播の代替として優れた方法です。
いくつかの課題を試してみる
Challenge 1 of 2:イベントハンドラーを修正する #
このボタンをクリックすると、ページの背景が白と黒で切り替わるはずです。しかし、クリックしても何も起こりません。問題を修正してください。(handleClick内部のロジックについては心配しないでください—その部分は問題ありません。)
