React + react-use: useKeyフックでキーボードイベントを扱う

[`react-use`](https://github.com/streamich/react-use#--------------------react-use------------------)は、便利なフックを詰め合わせたライブラリです。本稿はその中から、キーボードイベントが簡単に扱える[`useKey`](https://github.com/streamich/react-use/blob/master/docs/useKey.md#usekey)をご紹介します。作例は[CodeSandbox](https://codesandbox.io/s/react-react-use-using-usekey-to-deal-with-keyboard-events-coq74)に公開しました。

# Create React Appでカウンターアプリケーションをつくる
サンプルにするのは、よくとり上げられるカウンターです。React公式サイトの「[ステートフックの利用法](https://ja.reactjs.org/docs/hooks-state.html)」でも、`useState`フックの説明に使われています。

Reactアプリケーションのひな型は、モジュール分けできる[Create Reac App](https://ja.reactjs.org/docs/create-a-new-react-app.html#create-react-app)でつくることにしましょう(「Create React App 入門 01: 3×3マスのゲーム盤をつくる」01「[Reactアプリケーションのひな形をつくる](https://fumiononaka.com/Business/html5/FN2101001.html#sec01)」参照)。

```terminal
npx create-react-app react-usekey
```

ルートコンポーネントのモジュール`src/App.js`は、つぎのコード001のように書き改めます。`useState`でカウンター数値の状態変数(`count`)をひとつ定め、減算(`decrement`)と加算(`increment`)のハンドラを加えました。`CounterDisplay`は、このあと新たにつくるカウンター表示のコンポーネントです。

#### コード001■useStateとプロパティを用いたルートモジュール
```react:src/App.js
import React, { useState } from 'react';
import CounterDisplay from './CounterDisplay';

const initialCount = 0;
function App() {
	const [count, setCount] = useState(initialCount);
	const decrement = () => setCount((count) => --count);
	const increment = () => setCount((count) => ++count);
	return (
		<div className="App">
			<CounterDisplay counter={{ count, decrement, increment }} />
		</div>
	);
}

export default App;
```

カウンターを表示するコンポーネントのモジュール`src/CounterDisplay.js`は、つぎのコード002のように定めました。コンポーネントが受け取ったプロパティ(`counter`)から、カウンター数値(`count`)と減算(`decrement`)・加算(`increment`)のハンドラを取り出して、JSXのテキストとボタンに与えましょう。

#### コード002■カウンター表示のモジュール
```react:src/CounterDisplay.js
import React from "react";

const CounterDisplay = ({ counter }) => {
	return (
		<div>
			<button onClick={counter.decrement}>-</button>
			<span>{counter.count}</span>
			<button onClick={counter.increment}>+</button>
		</div>
	);
}
export default CounterDisplay;
```

これで、数値をボタンで増減できるカウンターのでき上がりです(図001)。まだ、キーボードイベントには対応していません。

#### 図001■でき上がったカウンター
![2009001_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/90b3efbd-b3e1-d74c-8301-493b54c03298.png)


# react-useのuseKeyフックを使う
では、いよいよ`useKey`フックを使います。まずは、アプリケーションへの`react-use`のインストールです。

```terminal
npm install react-use
```

そうしたら、キーボードイベントを扱いたいコンポーネントに、`useKey`を`import`してください。上下の矢印キーで、カウンターを増減したいと思います。

`useKey`の使い方は、つぎのように気が抜けるほど簡単です。第1引数にキーを示す[`KeyboardEvent.key`](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/key)プロパティの値、第2引数にイベントハンドラを渡します。JSXのどの要素にイベントハンドラを加えるか、などと悩まなくて済むのです。

```react:src/App.js
import { useKey } from 'react-use';

function App() {

	useKey('ArrowDown', decrement);
	useKey('ArrowUp', increment);

}
```

ここまでのルートモジュール`src/App.js`の記述全体を、つぎのコード003にまとめました。

#### コード003■上下矢印キーでカウンターを増減させるルートモジュール
```react:src/App.js
import React, { useState } from 'react';
import {useKey} from 'react-use';
import CounterDisplay from './CounterDisplay';

const initialCount = 0;
function App() {
	const [count, setCount] = useState(initialCount);
	const decrement = () => setCount((count) => --count);
	const increment = () => setCount((count) => ++count);
	useKey('ArrowDown', decrement);
	useKey('ArrowUp', increment);
	return (
		<div className="App">
			<CounterDisplay counter={{ count, decrement, increment }} />
		</div>
	);
}

export default App;
```


# [control]/[Ctrl] + [esc]キーでカウンターをリセットする
キーボードイベントの処理をもうひとつ加えます。[control]/[Ctrl] + [esc]キーを押したら、カウンターの数値を0にリセットしましょう。[esc]キーの`KeyboardEvent.key`プロパティの値は`Escape`です。[control]/[Ctrl]キーを押しているかどうかは、[`KeyboardEvent.ctrlKey`](https://developer.mozilla.org/ja/docs/Web/API/KeyboardEvent#methods)で調べられます。

`useKey`のつぎの構文で、第1引数はキー判定する関数です。キーイベントを受け取るので、ハンドラを呼び出すキーかどうか論理値で返します。第3引数の`event`プロパティに定めるのは、`keydown`/`keypress`/`keyup`のいずれかの[キーボードイベント](https://developer.mozilla.org/docs/Web/API/KeyboardEvent)です。

```js
const キー判定関数 = (event) => 判定した論理値;
useKey(キー判定関数, イベントハンドラ, {event: イベント});
```

キー判定関数では、つぎのように[control]/[Ctrl] + [esc]キーをイベント(`event`)のプロパティから調べます。キーボードイベントは`keyup`としました。ルートモジュールの記述は、以下のコード004にまとめたとおりです。作例は[CodeSandbox](https://codesandbox.io/s/react-react-use-using-usekey-to-deal-with-keyboard-events-coq74)に公開しましたので、動きやモジュールごとのコードはこちらでお確かめください。

```react:src/App.js
function App() {

	const predicate = (event) => event.ctrlKey && event.key === 'Escape';
	const escKeyUpHandler = () => setCount(0);
	useKey(predicate, escKeyUpHandler, {event: 'keyup'});

}
```

#### コード004■[control]/[Ctrl] + [esc]キーでカウンターをリセットするルートモジュール
```react:src/App.js
import React, { useState } from 'react';
import {useKey} from 'react-use';
import CounterDisplay from './CounterDisplay';

const initialCount = 0;
function App() {
	const [count, setCount] = useState(initialCount);
	const decrement = () => setCount((count) => --count);
	const increment = () => setCount((count) => ++count);
	useKey('ArrowDown', decrement);
	useKey('ArrowUp', increment);
	const predicate = (event) => event.ctrlKey && event.key === 'Escape';
	const escKeyUpHandler = () => setCount(0);
	useKey(predicate, escKeyUpHandler, {event: 'keyup'});
	return (
		<div className="App">
			<CounterDisplay counter={{ count, decrement, increment }} />
		</div>
	);
}

export default App;
```

# オブジェクトの分割代入を使う
ここで、[オブジェクトの分割代入](https://qiita.com/FumioNonaka/items/58358a29850afd7a0f37#%E5%88%86%E5%89%B2%E4%BB%A3%E5%85%A5%E3%82%92%E4%BD%BF%E3%81%86)の構文をご紹介しましょう。前掲コード004のキー判定関数(`predicate()`)で、引数のキーボードイベントからプロパティを取り出すコードです。オブジェクトの分割代入を使えば、つぎのように引数からプロパティが簡単に取り出せます。

```react
// const predicate = (event) => event.ctrlKey && event.key === "Escape";
const predicate = ({ ctrlKey, key }) => ctrlKey && key === "Escape";
```

[追記: 2021年01月30日]「オブジェクトの分割代入を使う」の項を追加。