イベントへの応答
Reactでは、JSXにイベントハンドラを追加することができます。イベントハンドラとは、クリック、ホバー、フォーム入力へのフォーカスなどのインタラクションに応答してトリガーされる独自の関数のことです。
学習内容
- イベントハンドラを記述するさまざまな方法
- 親コンポーネントからイベント処理ロジックを渡す方法
- イベントの伝播方法と停止方法
イベントハンドラの追加
イベントハンドラを追加するには、まず関数を定義し、適切なJSXタグにpropとして渡します。たとえば、これはまだ何も行わないボタンです。
export default function Button() { return ( <button> I don't do anything </button> ); }
次の3つの手順に従うと、ユーザーがクリックしたときにメッセージを表示できます。
handleClick
という関数を、Button
コンポーネント内で宣言します。- その関数内にロジックを実装します(メッセージを表示するには
alert
を使用します)。 <button>
JSXにonClick={handleClick}
を追加します。
export default function Button() { function handleClick() { alert('You clicked me!'); } return ( <button onClick={handleClick}> Click me </button> ); }
handleClick
関数を定義し、<button>
にpropとして渡しました。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
propでアラートを表示するボタンを以下に示します。
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
コンポーネントを使用するかによって、異なる関数を実行したい場合があります。おそらく、1つはムービーを再生し、もう1つは画像をアップロードするといった具合です。
これを行うには、親からコンポーネントが受け取るpropを次のようにイベントハンドラーとして渡します
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
は、内部のButton
へのonClick
propとしてhandlePlayClick
を渡します。UploadButton
は、内部のButton
へのonClick
propとして() => alert('Uploading!')
を渡します。
最後に、Button
コンポーネントはonClick
というpropを受け入れます。このpropを、onClick={onClick}
とともに、組み込みブラウザの<button>
に直接渡します。これにより、Reactはクリック時に渡された関数を呼び出すように指示されます。
デザインシステムを使用している場合、ボタンなどのコンポーネントはスタイリングを含みますが、動作を指定しないのが一般的です。代わりに、PlayButton
やUploadButton
のようなコンポーネントは、イベントハンドラーを下に渡します。
イベントハンドラーpropの名前付け
<button>
や<div>
のような組み込みコンポーネントは、ブラウザのイベント名であるonClick
のようなものしかサポートしていません。ただし、独自のコンポーネントを構築する場合は、イベントハンドラーのpropに好きな名前を付けることができます。
慣例として、イベントハンドラーのpropはon
で始まり、その後に大文字が続く必要があります。
たとえば、Button
コンポーネントのonClick
propは、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>
(小文字)が、引き続きonClick
というpropを必要としていることを示していますが、カスタムButton
コンポーネントが受け取るpropの名前は、自由に決めることができます。
コンポーネントが複数のインタラクションをサポートしている場合、アプリ固有の概念に合わせてイベントハンドラーのpropに名前を付けることができます。たとえば、この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> ); }
App
コンポーネントは、Toolbar
がonPlayMovie
またはonUploadImage
をどのように使用するかを知る必要がないことに注意してください。それは、Toolbar
の実装の詳細です。ここでは、Toolbar
はonClick
ハンドラーとしてそれらをButton
に渡しますが、後でキーボードショートカットでトリガーすることもできます。onPlayMovie
のようなアプリ固有のインタラクション後にpropに名前を付けると、後でそれらがどのように使用されるかを変更する柔軟性が得られます。
イベントの伝播
イベントハンドラーは、コンポーネントが持つ可能性のあるすべての子要素からのイベントもキャッチします。イベントはツリーを「バブルアップ」または「伝播」すると言います。イベントが発生した場所から始まり、ツリーを上っていきます。
この <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()
を呼び出し、イベントがさらにバブリングするのを防ぎます。onClick
関数を呼び出します。これは、Toolbar
コンポーネントから渡されたプロップです。
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
ハンドラーを呼び出します。
キャプチャイベントは、ルーターや分析などのコードに役立ちますが、アプリコードではおそらく使用しないでしょう。
伝播の代替としてのハンドラーの渡し方
このクリックハンドラーが、コード行を1つ実行した後、親から渡された onClick
プロップを呼び出す方法に注目してください
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
このハンドラーに、親の onClick
イベントハンドラーを呼び出す前に、さらにコードを追加することもできます。このパターンは、伝播の代替を提供します。これにより、子コンポーネントがイベントを処理できると同時に、親コンポーネントがいくつかの追加の動作を指定できます。伝播とは異なり、自動ではありません。ただし、このパターンの利点は、イベントの結果として実行されるコードのチェーン全体を明確に追跡できることです。
伝播に依存しており、どのハンドラーが実行され、その理由を追跡するのが難しい場合は、代わりにこのアプローチを試してください。
デフォルトの動作の防止
一部のブラウザイベントには、関連付けられたデフォルトの動作があります。たとえば、<form>
の送信イベント(内部のボタンがクリックされたときに発生します)は、デフォルトでページ全体をリロードします。
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()
は、デフォルトのブラウザの動作を持ついくつかのイベントに対して、その動作を抑制します。
イベントハンドラーは副作用を持つことができますか?
もちろんです! イベントハンドラーは副作用を起こすのに最適な場所です。
レンダリング関数とは異なり、イベントハンドラーは純粋である必要がないため、何かを変更するのに最適な場所です。例えば、入力に応じて入力値を変更したり、ボタンの押下に応じてリストを変更したりできます。ただし、情報を変更するためには、まず情報を保存する方法が必要です。Reactでは、これはステート、コンポーネントのメモリを使用することで行われます。これについては、次のページで詳しく説明します。
まとめ
- イベントを処理するには、
<button>
などの要素にプロパティとして関数を渡します。 - イベントハンドラーは、呼び出すのではなく、渡す必要があります!
onClick={handleClick}
のように渡し、onClick={handleClick()}
のように呼び出してはいけません。 - イベントハンドラー関数は、個別に定義することも、インラインで定義することもできます。
- イベントハンドラーはコンポーネント内で定義されるため、プロパティにアクセスできます。
- 親でイベントハンドラーを宣言し、それをプロパティとして子に渡すことができます。
- アプリケーション固有の名前で独自のイベントハンドラープロパティを定義できます。
- イベントは上位に伝播します。それを防ぐには、最初の引数に対して
e.stopPropagation()
を呼び出してください。 - イベントには、望ましくないデフォルトのブラウザの動作がある場合があります。それを防ぐには、
e.preventDefault()
を呼び出してください。 - 子ハンドラーからイベントハンドラープロパティを明示的に呼び出すことは、伝播の良い代替手段です。
チャレンジ 1of 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> ); }