イベントへの応答
React では、JSX にイベントハンドラを追加できます。イベントハンドラとはあなた自身で書く関数であり、クリック、ホバー、フォーム入力へのフォーカスといったユーザインタラクションに応答してトリガされます。
このページで学ぶこと
- イベントハンドラを記述するさまざまな方法
- 親コンポーネントからイベント処理ロジックを渡す方法
- イベントの伝播のしかたとそれを停止する方法
イベントハンドラの追加
イベントハンドラを追加するには、まず関数を定義し、適切な JSX タグに props の一部として渡します。例として、以下にまだ何もしないボタンを示します。
ユーザがクリックするとメッセージが表示されるようにするには、以下の 3 つの手順を実行します。
Button
コンポーネントの内部でhandleClick
という関数を宣言します。- その関数内にロジックを実装します(ここでは
alert
を使ってメッセージを表示)。 <button>
の JSX にonClick={handleClick}
を追加します。
handleClick
関数を定義し、それを <button>
に props の一部として渡しました。この handleClick
がイベントハンドラです。イベントハンドラ関数は:
- 通常、コンポーネントの内部で定義されます。
- イベント名の先頭に
handle
が付いた名前にします。
慣習的に、イベントハンドラは handle
の後ろにイベントの名称をつなげた名前にすることが一般的です。onClick={handleClick}
、onMouseEnter={handleMouseEnter}
などがよく見られます。
また、JSX の中でイベントハンドラをインラインで定義することもできます。
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
または、より簡潔にアロー関数を使って記述することもできます。
<button onClick={() => {
alert('You clicked me!');
}}>
これらのスタイルはすべて同等です。インラインのイベントハンドラは、短い関数の場合に便利です。
イベントハンドラでの props の読み取り
イベントハンドラはコンポーネント内に宣言されているため、コンポーネントの props にアクセスできます。以下は、クリックされたときに message
プロパティの内容をアラートで表示するボタンです。
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="Playing!"> Play Movie </AlertButton> <AlertButton message="Uploading!"> Upload Image </AlertButton> </div> ); }
これにより、これらの 2 つのボタンは異なるメッセージを表示できます。渡されるメッセージを変更してみてください。
イベントハンドラを props として渡す
よくあるケースとして、親コンポーネントが子のイベントハンドラを指定したい場合があります。ボタンを考えてみましょう:Button
というコンポーネントには、使用する場所によって、動画を再生する、画像をアップロードするなど、異なる関数を実行させたいことでしょう。
これを行うには、以下のようにして、コンポーネントが親から受け取った props の一部をイベントハンドラとして渡します:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`Playing ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Play "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('Uploading!')}> Upload Image </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="Kiki's Delivery Service" /> <UploadButton /> </div> ); }
この例では、Toolbar
コンポーネントは PlayButton
と UploadButton
をレンダーしています:
PlayButton
はhandlePlayClick
を内部Button
のonClick
プロパティとして渡しています。UploadButton
は() => alert('Uploading!')
を内部Button
のonClick
プロパティとして渡しています。
最後に、Button
コンポーネントは props として onClick
を受け取ります。それを onClick={onClick}
としてブラウザの組み込み <button>
に直接渡しています。これにより、クリック時に React は渡された関数を呼び出すようになります。
デザインシステムを使用している場合、ボタンなどのコンポーネントはスタイリングを含んでいるが振る舞いは指定されないことが一般的です。代わりに、PlayButton
や UploadButton
のようなコンポーネントがイベントハンドラを渡します。
イベントハンドラの props の命名
<button>
や <div>
のような組み込みコンポーネントは、onClick
のようなブラウザのイベント名のみをサポートしています。ただし、独自のコンポーネントを作成する場合、イベントハンドラとなる props を、好きなように命名できます。
慣習として、イベントハンドラのプロップは on
で始まり、次に大文字の文字が続くようにします。
たとえば、Button
コンポーネントの props である onClick
は onSmash
と命名することも可能です:
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('Playing!')}> Play Movie </Button> <Button onSmash={() => alert('Uploading!')}> Upload Image </Button> </div> ); }
この例の <button onClick={onSmash}>
を見ると分かるように、ブラウザの <button>
(小文字)は常に props として onClick
が必要ですが、カスタム Button
コンポーネントが受け取る props の名前はあなた次第です!
コンポーネントが複数種類のインタラクションをサポートする場合、イベントハンドラの props をアプリ固有の概念に基づいて命名することができます。例えば、この Toolbar
コンポーネントは onPlayMovie
と onUploadImage
というイベントハンドラを受け取ります:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Playing!')} onUploadImage={() => alert('Uploading!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Play Movie </Button> <Button onClick={onUploadImage}> Upload Image </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Toolbar
が onPlayMovie
や onUploadImage
をどう扱うのかを、App
コンポーネントが知る必要がないことに注意してください。それは Toolbar
の実装の詳細です。ここでは、Toolbar
はそれらを Button
の onClick
ハンドラとして渡していますが、後でキーボードショートカットでもそれらをトリガするようにすることができます。onPlayMovie
のようなアプリ固有のインタラクションに基づいて props を名付けることで、後でどのように使用されるかを変更できるという柔軟性が得られます。
イベント伝播
イベントハンドラは、コンポーネントが持っている可能性のあるどの子が由来であってもイベントをキャッチします。このことをイベントがツリーを “バブリング (bubble)” または “伝播 (propagate)” する、と表現します。イベントは発生した場所から始まり、ツリーを上に向かって進んでいきます。
この <div>
には 2 つのボタンが含まれています。<div>
も各ボタンも、それぞれ onClick
ハンドラを持っていますね。ボタンをクリックしたとき、どのハンドラが実行されると思いますか?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <button onClick={() => alert('Playing!')}> Play Movie </button> <button onClick={() => alert('Uploading!')}> Upload Image </button> </div> ); }
どちらのボタンをクリックしても、最初にそれ自体の onClick
が実行され、その後で親である <div>
の onClick
が実行されます。つまりメッセージが 2 つ表示されます。ツールバー自体をクリックすると、親の <div>
の onClick
のみが実行されます。
伝播の停止
イベントハンドラは、イベントオブジェクトを唯一の引数として受け取ります。慣習的に、それは “event” を意味する e
と呼ばれています。このオブジェクトを使用して、イベントに関する情報を読み取ることができます。
このイベントオブジェクトを使い、伝播を止めることもできます。イベントが親コンポーネントに伝わらないようにしたい場合、以下の Button
コンポーネントのようにして e.stopPropagation()
を呼び出す必要があります:
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <Button onClick={() => alert('Playing!')}> Play Movie </Button> <Button onClick={() => alert('Uploading!')}> Upload Image </Button> </div> ); }
ボタンをクリックすると以下のことが起こります。
- React が
<button>
に渡されたonClick
ハンドラを呼び出す。 - そのハンドラは
Button
で定義されており、次のことを行う。e.stopPropagation()
を呼び出し、イベントがさらにバブリングされるのを防ぐ。Toolbar
コンポーネントから渡された props であるonClick
関数を呼び出す。
- その関数は
Toolbar
コンポーネントで定義されており、そのボタン固有のアラートを表示する。 - 伝播が停止されたため、親の
<div>
のonClick
ハンドラは実行されない。
e.stopPropagation()
の結果、ボタンをクリックすると、アラートが 2 つ(<button>
と親のツールバーの <div>
から)ではなく、1 つだけ(<button>
のみから)表示されるようになります。ボタンをクリックすることと、周囲のツールバーの余白をクリックすることは別物なので、この UI では伝播を止めることが理にかなっています。
さらに深く知る
まれに、伝播が停止されても子要素のすべてのイベントをキャッチしたいという場合があります。たとえば、伝播のロジックに関係なく、すべてのクリックを分析のため記録したい場合などです。これを行うには、イベント名の末尾に Capture
を追加します。
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
すべてのイベントは 3 つのフェーズで伝播します。
- 下方向に移動し、すべての
onClickCapture
ハンドラを呼び出す。 - クリックされた要素自体の
onClick
ハンドラを実行する。 - 上方向に移動し、すべての
onClick
ハンドラを呼び出す。
キャプチャイベントはルータや分析のようなコードで役立ちますが、アプリケーションコードで使用することはほとんどありません。
伝播の代わりにハンドラを渡す
このクリックハンドラは、親から渡された onClick
を呼び出す前に、1 行コードを実行していることに注目してください。
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
親の onClick
イベントハンドラを呼び出す前に、このハンドラにさらにコードを追加することもできるでしょう。このパターンは、伝播の代替手段になります。子コンポーネントにイベントを処理させつつ、親コンポーネントが追加の動作を指定できるようになるのです。伝播とは異なり、これは自動的に起こることではありません。しかし、このパターンの利点は、あるイベントが発生した結果として実行されるコードのすべての繋がりをはっきりと追跡できることです。
イベント伝播に依存していて、どのハンドラがどのような理由で実行されているのかを追跡することが困難な場合は、代わりにこのアプローチを試してください。
デフォルト動作を防ぐ
ブラウザのイベントには、デフォルトの動作が関連付けられているものがあります。例えば、<form>
の submit イベントは、その中のボタンがクリックされると、デフォルトではページ全体がリロードされます。
export default function Signup() { return ( <form onSubmit={() => alert('Submitting!')}> <input /> <button>Send</button> </form> ); }
イベントオブジェクトの e.preventDefault()
を呼び出して、これを防ぐことができます。
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Submitting!'); }}> <input /> <button>Send</button> </form> ); }
e.stopPropagation()
と e.preventDefault()
を混同しないでください。どちらも有用ですが、両者は無関係です。
e.stopPropagation()
は、ツリーの上側にあるタグにアタッチされたイベントハンドラが発火しないようにします。e.preventDefault()
は、数は少ないですがイベントがブラウザデフォルトの動作を持っていた場合に、それを抑制します。
イベントハンドラは副作用を持っていても構わない?
もちろんです! イベントハンドラは副作用のための最適な場所です。
レンダー関数とは異なり、イベントハンドラは純関数 (pure function) である必要はないので、何かを変更するのに最適な場所です。たとえば、入力値をタイプに応じて変更する、ボタンの押下に応じてリストを変更する、などです。ただし、情報を変更するためには、まずそれを格納する方法が必要です。React では、これは state、コンポーネントのメモリを使用して行います。次のページでそのすべてを学びます。
まとめ
- イベントは、
<button>
などの要素に関数を props として渡すことで処理できます。 - イベントハンドラは渡す必要があります。呼び出してはいけません!
onClick={handleClick}
とするのであって、onClick={handleClick()}
としてはいけません。 - イベントハンドラ関数は別途定義することも、インラインで定義することもできます。
- イベントハンドラはコンポーネント内に定義されているため、props にアクセスできます。
- 親でイベントハンドラを宣言し、子に props として渡すことができます。
- イベントハンドラ props を定義する際にアプリケーション固有の名前をつけることができます。
- イベントは上方向に伝播します。これを防ぐには、最初の引数を使って
e.stopPropagation()
を呼び出します。 - イベントは、望ましくないデフォルトのブラウザ動作を持つことがあります。これを防ぐには、
e.preventDefault()
を呼ぶ必要があります。 - 子コンポーネントでイベントハンドラを定義して props から受け取ったハンドラを明示的に呼び出すことは、伝播の代替手段として良い方法です。
チャレンジ 1/2: イベントハンドラを修正
このボタンをクリックすると、ページの背景が白と黒の間で切り替わることになっています。ただし、クリックしても何も起こりません。問題を修正してください(handleClick
内部のロジックについては心配無用です。そこは問題ありません)。
export default function LightSwitch() { function handleClick() { let bodyStyle = document.body.style; if (bodyStyle.backgroundColor === 'black') { bodyStyle.backgroundColor = 'white'; } else { bodyStyle.backgroundColor = 'black'; } } return ( <button onClick={handleClick()}> Toggle the lights </button> ); }