Reactでは、JSXにイベントハンドラを追加することができます。イベントハンドラとは、クリック、ホバー、フォーム入力へのフォーカスなどのインタラクションに応答してトリガーされる独自の関数のことです。

学習内容

  • イベントハンドラを記述するさまざまな方法
  • 親コンポーネントからイベント処理ロジックを渡す方法
  • イベントの伝播方法と停止方法

イベントハンドラの追加

イベントハンドラを追加するには、まず関数を定義し、適切なJSXタグにpropとして渡します。たとえば、これはまだ何も行わないボタンです。

export default function Button() {
  return (
    <button>
      I don't do anything
    </button>
  );
}

次の3つの手順に従うと、ユーザーがクリックしたときにメッセージを表示できます。

  1. handleClickという関数を、Buttonコンポーネントで宣言します。
  2. その関数内にロジックを実装します(メッセージを表示するにはalertを使用します)。
  3. <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!');
}}>

これらのスタイルはすべて同等です。インラインイベントハンドラは、短い関数に便利です。

落とし穴

イベントハンドラに渡される関数は、呼び出さずに渡す必要があります。例:

関数を渡す(正しい)関数を呼び出す(正しくない)
<button onClick={handleClick}><button onClick={handleClick()}>

違いは微妙です。最初の例では、handleClick関数がonClickイベントハンドラとして渡されます。これにより、Reactはそれを記憶し、ユーザーがボタンをクリックしたときにのみ関数を呼び出すようになります。

2番目の例では、handleClick()の末尾にある()が、クリックなしでレンダリング中に、関数を即座に起動します。これは、JSXの{}内のJavaScriptがすぐに実行されるためです。

コードをインラインで記述する場合、同じ落とし穴が別の形で現れます。

関数を渡す(正しい)関数を呼び出す(正しくない)
<button onClick={() => alert('...')}><button onClick={alert('...')}>

このようにインラインコードを渡すと、クリック時に起動せず、コンポーネントがレンダリングされるたびに起動します。

// This alert fires when the component renders, not when clicked!
<button onClick={alert('You clicked me!')}>

イベントハンドラをインラインで定義する場合は、次のように匿名関数でラップします。

<button onClick={() => alert('You clicked me!')}>

これにより、すべてのレンダリングで内部のコードを実行するのではなく、後で呼び出す関数が作成されます。

どちらの場合も、渡したいのは関数です。

  • <button onClick={handleClick}>は、handleClick関数を渡します。
  • <button onClick={() => alert('...')}> は、() => alert('...') 関数を渡します。

アロー関数についてもっと読む。

イベントハンドラーでの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コンポーネントは、PlayButtonUploadButtonをレンダリングします。

  • PlayButtonは、内部のButtonへのonClick propとしてhandlePlayClickを渡します。
  • UploadButtonは、内部のButtonへのonClick propとして() => alert('Uploading!')を渡します。

最後に、ButtonコンポーネントはonClickというpropを受け入れます。このpropを、onClick={onClick}とともに、組み込みブラウザの<button>に直接渡します。これにより、Reactはクリック時に渡された関数を呼び出すように指示されます。

デザインシステムを使用している場合、ボタンなどのコンポーネントはスタイリングを含みますが、動作を指定しないのが一般的です。代わりに、PlayButtonUploadButtonのようなコンポーネントは、イベントハンドラーを下に渡します。

イベントハンドラー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コンポーネントは、onPlayMovieonUploadImageイベントハンドラーを受け取ります。

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コンポーネントは、ToolbaronPlayMovieまたはonUploadImageどのように使用するかを知る必要がないことに注意してください。それは、Toolbarの実装の詳細です。ここでは、ToolbaronClickハンドラーとしてそれらをButtonに渡しますが、後でキーボードショートカットでトリガーすることもできます。onPlayMovieのようなアプリ固有のインタラクション後にpropに名前を付けると、後でそれらがどのように使用されるかを変更する柔軟性が得られます。

イベントハンドラーに適切なHTMLタグを使用してください。たとえば、クリックを処理するには、<div onClick={handleClick}>ではなく<button onClick={handleClick}>を使用してください。実際のブラウザの<button>を使用すると、キーボードナビゲーションなどの組み込みブラウザの動作が有効になります。ボタンのデフォルトのブラウザスタイルが気に入らず、リンクや他のUI要素のように見せたい場合は、CSSで実現できます。アクセシブルなマークアップの記述について詳細はこちらをご覧ください。

イベントの伝播

イベントハンドラーは、コンポーネントが持つ可能性のあるすべての子要素からのイベントもキャッチします。イベントはツリーを「バブルアップ」または「伝播」すると言います。イベントが発生した場所から始まり、ツリーを上っていきます。

この <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 のみが実行されます。

落とし穴

Reactでは、onScroll を除いて、すべてのイベントが伝播します。onScroll は、アタッチしたJSXタグでのみ機能します。

伝播の停止

イベントハンドラーは、唯一の引数としてイベントオブジェクトを受け取ります。慣例として、通常は “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>
  );
}

ボタンをクリックすると

  1. Reactは、<button> に渡された onClick ハンドラーを呼び出します。
  2. Button で定義されたそのハンドラーは、次のことを行います
    • e.stopPropagation() を呼び出し、イベントがさらにバブリングするのを防ぎます。
    • onClick 関数を呼び出します。これは、Toolbar コンポーネントから渡されたプロップです。
  3. Toolbar コンポーネントで定義されたその関数は、ボタン独自の警告を表示します。
  4. 伝播が停止したため、親の <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つのフェーズで伝播します

  1. 下方向に移動し、すべての onClickCapture ハンドラーを呼び出します。
  2. クリックされた要素の onClick ハンドラーを実行します。
  3. 上方向に移動し、すべての 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>
  );
}