React + TypeScript: 発生したイベントを処理する

React公式サイトのドキュメントが2023年3月16日に改訂されました(「[Introducing react.dev](https://react.dev/blog/2023/03/16/introducing-react-dev)」参照)。本稿は、基本解説の「[Responding to Events](https://react.dev/learn/responding-to-events)」をかいつまんでまとめた記事です。ただし、コードにはTypeScriptを加えました。反面、初心者向けのJavaScriptの基礎的な説明は省いています。

なお、本シリーズ解説の他の記事については「[React + TypeScript: React公式ドキュメントの基本解説『Learn React』を学ぶ](https://qiita.com/FumioNonaka/items/d70585bd3c0a6d4edc24)」をご参照ください。

この記事で解説するのは、つぎのような項目です。

* イベントハンドラをどう定め方るか。
* イベント処理のロジックを親コンポーネントから子にどのように渡すか。
* イベントの制御の仕方。

Reactのイベントハンドラは、JSXにプロパティとして加え、ハンドラ(コールバック)関数を与えます。要素へのマウスクリックやポインタの重なり、フォーム入力のフォーカスといったインタラクションなどに反応して呼び出される関数です。


# イベントハンドラを加える

イベントハンドラはつぎのように加えてください。

* コンポーネントにハンドラ(コールバック)関数を定めます。
* 関数の参照を与えるのはJSXに書いたタグのプロパティです(「[Passing Props to a Component](https://react.dev/learn/passing-props-to-a-component)」参照)。

つぎのコード例は、ボタン(`<button>`)のクリックイベント(`onClick`)にハンドラ関数(`handleClick`)を定めました。

```react:src/App.tsx
import { MouseEventHandler } from 'react';

export default function App() {
	const handleClick: MouseEventHandler = () => {
		alert('You clicked me!');
	};
	return <button onClick={handleClick}>Click me</button>;
}
```

React公式サイトでは、イベントハンドラの関数とプロパティについて、つぎのような名前のつけ方を慣習として紹介しています。

* 関数: `handle`ではじまって、あとにイベント名を加えたキャメルケース(「[Adding event handlers](https://beta.reactjs.org/learn/responding-to-events#adding-event-handlers)」参照)。
* プロパティ: `on`ではじまるキャメルケース(「[Naming event handler props](https://beta.reactjs.org/learn/responding-to-events#naming-event-handler-props)」参照)。


# イベントハンドラでプロパティを読み込む
イベントハンドラは、コンポーネントの中に定められます。つまり、コンポーネントのプロパティが参照できるということです。つぎのコードは、ボタンの子コンポーネント(`AlertButton`)に渡したプロパティ(`message`)のテキストを、クリックで開く警告ダイアログに示します。同じコンポーネントでも、異なるプロパティ値によって表示が変えられる例です。

```react:src/App.tsx
import { AlertButton } from './AlertButton';

export default function Toolbar() {
	return (
		<div>
			<AlertButton message="Playing!">Play Movie</AlertButton>
			<AlertButton message="Uploading!">Upload Image</AlertButton>
		</div>
	);
}
```

```react:src/AlertButton.tsx
import { FC, ReactNode } from 'react';

type Props = {
	message: string;
	children: ReactNode;
};
export const AlertButton: FC<Props> = ({ message, children }) => {
	return <button onClick={() => alert(message)}>{children}</button>;
};
```


# イベントハンドラをプロパティとして渡す
子コンポーネントで起こるイベントを親のハンドラで扱いたいことがあるでしょう。そのような場合、ハンドラ関数を小コンポーネントにプロパティで渡してください。

つぎのコード例では、親コンポーネント(`Toolbar`)が、ハンドラ関数(`handleClick`)を子(`AlertButton`)にプロパティ`onClick`として渡しました。すると、子コンポーネントは、イベント(`onClick`)が発生したとき、受け取った関数を呼び出せばよいのです(サンプル001)。なお、親コンポーネントから子に与えるプロパティ名は(`onClick`でなくとも)、自由につけて差し支えありません。

```react:src/App.tsx
import { AlertButton } from './AlertButton';

export default function Toolbar() {
	const handleClick = (message: string) => {
		alert(`Playing ${message}!`);
	};
	return (
		<div>
			<AlertButton message="Kiki's Delivery Service" onClick={handleClick}>
				Play
			</AlertButton>
		</div>
	);
}
```

```react:src/AlertButton.tsx
import { FC, ReactNode } from 'react';

type Props = {
	message: string;
	children: ReactNode;
	onClick: (message: string) => void;
};
export const AlertButton: FC<Props> = ({ message, children, onClick }) => {
	return (
		<button onClick={() => onClick(message)}>
			{children} "{message}"
		</button>
	);
};
```

#### サンプル001■React + TypeScript: Responding to Events 01

https://codesandbox.io/embed/react-typescript-responding-to-events-01-f3131z?fontsize=14&hidenavigation=1&theme=dark


# イベントの制御
Reactにおけるイベントの制御は、基本的に標準JavaScriptにしたがいます。詳しくは、以下のリンク先ドキュメントをご参照ください。

* [`Event.stopPropagation()`](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation): 現行のイベントがさらに伝播するのを止めます。
* [`Event.preventDefault()`](https://developer.mozilla.org/docs/Web/API/Event/preventDefault): イベントが明示的に処理されない場合に、通常と異なりブラウザの既定のアクションを実行しません((「[Preventing default behavior](https://beta.reactjs.org/learn/responding-to-events#preventing-default-behavior)」参照)。

Reactでイベントは原則として子コンポーネントから親に伝わります(「バブリングフェーズ」という流れです)。つぎのコード例では、`onClick`イベントハンドラを、親(`<div>`)と子(`<button>`)の両方の要素に加えました。このとき、ボタンをクリックすると、まず子のハンドラ、つぎに親のハンドラが呼び出されるということです。

```react:src/App.tsx
export default function App() {
	return (
		<div
			onClick={() => {
				alert("The parent is called");
			}}
		>
			<button
				onClick={() => {
					alert("The child is called");
				}}
			>
				Button
			</button>
		</div>
	);
}
```

そこで、子(`<button>`)のハンドラ関数のはじめに`Event.stopPropagation()`メソッドを加えてみましょう。すると、`onClick`イベントは親(`<div>`)に伝わらなくなり、親のハンドラは呼び出されません。

```react:src/App.tsx
<button
	// onClick={() => {
	onClick={(event) => {
		event.stopPropagation();
		alert("The child is called");
	}}
>
	Button
</button>
```

イベントの流れにはもうひとつ「キャプチャリングフェーズ」があります。イベントを受け取るのは親が先で、子はあとです。この場合、イベント名の末尾に`Capture`を加えます(「[Capture phase events](https://beta.reactjs.org/learn/responding-to-events#capture-phase-events)」)。イベントのバブリングとキャプチャリングについて、詳しくは「[イベントへの入門](https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks/Events)」をお読みください。

つぎのコード例ではボタンクリックで、まず親(`<div>`)の`onClickCapture`が呼び出されます(サンプル002)。そのあと実行されるのが子(`<button>`)の`onClick`イベントハンドラです。けれど、関数のはじめに`Event.stopPropagation()`を呼び出しています。したがって、親の`onClick`にはイベントが渡りません。

```react:src/App.tsx
export default function App() {
	return (
		<div
			onClick={() => {
				alert('The parent is called');
			}}
			onClickCapture={() => {
				alert('Capture event is triggered');
			}}
		>
		<button
			onClick={(event) => {
				event.stopPropagation();
				alert('The child is called');
			}}
		>
			Button
		</button>
		</div>
	);
}
```

#### サンプル002■React + TypeScript: Responding to Events 02

https://codesandbox.io/embed/react-typescript-responding-to-events-02-7whlqk?fontsize=14&hidenavigation=1&theme=dark

`Event.preventDefault()`が使われる典型は、`<form>`の`onSubmit`イベントでしょう。規定のアクションとしては、ブラウザがページをすべて再読み込みしてしまいます。`Event.preventDefault()`の呼び出しにより、読み込む必要のあるコンポーネントや状態をReactが決められるのです。

イベントハンドラと副作用について触れておきましょう。イベントハンドラは、入力に応じた値の変更やボタンクリックによるデータの書き替えなど、副作用を実行するのに適しています。レンダリング関数とは異なり、ハンドラ関数は[純粋](https://beta.reactjs.org/learn/keeping-components-pure#where-you-_can_-cause-side-effects)でなくてよいからです(「[Can event handlers have side effects?](https://beta.reactjs.org/learn/responding-to-events#can-event-handlers-have-side-effects)」参照)。


# まとめ
この記事では、つぎのような項目についてご説明しました。

* イベントハンドラを定めるのはコンポーネントのプロパティです。
* イベントハンドラはコンポーネントが受け取ったプロパティを参照できます。
* 親コンポーネントに定めたハンドラ関数を子にプロパティとして渡しても構いません。
* イベントの制御は原則として標準JavaScriptにしたがいます。