React: コンポーネントのロジックをカスタムフックに切り出す ー カウンターの作例で

Reactに[フック](https://ja.reactjs.org/docs/hooks-intro.html)が採り入れられて、関数コンポーネントに状態をもたせられるようになりました。

「フックとは、関数コンポーネントにstateやライフサイクルといった Reactの機能を「接続する」(hook into)ための関数です」(「[要するにフックとは？](https://ja.reactjs.org/docs/hooks-overview.html#but-what-is-a-hook)」)。さらに、フックを独自につくって、コンポーネントからロジックを切り出すこともできます。そうすれば、コンポーネントのコードがすっきり見やすくなるとともに、そのカスタムフックを使い回すこともできるのです。

> 自分独自のフックを作成することで、コンポーネントからロジックを抽出して再利用可能な関数を作ることが可能です。
(「[独自フックの作成](https://ja.reactjs.org/docs/hooks-custom.html)」より)

本稿は簡単なカウンターの作例をとおして、フックの役割や考え方について解説します。


# Create React Appでアプリケーションのひな形をつくる
まず、Reactアプリケーションのひな形は、[Create React App](https://ja.reactjs.org/docs/create-a-new-react-app.html#create-react-app)でつくりましょう。コマンドラインツールで`npx create-react-app`につづけて、アプリケーション名(今回は`react-custom-hook`)を打ち込んでください。

```terminal
npx create-react-app react-custom-hook
```

アプリケーション名でつくられたディレクトリに切り替えて(`cd react-custom-hook`)、コマンド`yarn start`でひな形アプリケーションのページがローカルホスト(`http://localhost:3000/`)で開くはずです。


# `useState`とプロパティ(`props`)でカウンターをつくる
まずは、カスタムフックは用いず、`useState`とプロパティ(`props`)によりカウンターをつくりました(コード001)。

アプリケーションのモジュール`src/App.js`に`useState`で状態変数(`count`)を定め、関数としてはカウンターの減算(`decrement()`)と加算(`increment()`)が備わっています。それらをプロパティ(`counter`)として受け取るのが、このあと定めるカウンター表示のコンポーネント`CounterDisplay`です。

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

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

export default App;
```

カウンター表示のモジュール`src/CounterDisplay.js`は、アプリケーションから受け取ったプロパティ(`counter`)により、カウンタの値(`count`)表示と減算(`decrement`)・加算(`increment`)の処理を行います(コード002)。

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

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

これで簡単なカウンターができ上がりました(図001)。ただカウンターのカウントアップ・ダウンをするだけで、アプリケーションモジュールには、状態を使って何か行うという処理がありません。あとで加わるという想定にして、今回の作例からは省きました。また、CSS(`src/index.css`と`src/App.css`)は、基本的なフォントや余白の設定のみです。確かめたい方は、最後に掲げるCodeSandboxの作例([サンプル001](https://qiita.com/FumioNonaka/items/62285e474608388cbbe3#サンプル001カスタムフックを使ったカウンター))をご覧ください。

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


# アプリケーションのロジックをカスタムフックに切り出す
アプリケーションモジュール`src/App.js`のロジック、つまり状態変数とその処理関数を、このあと定めるカスタムフックに切り出しましょう。

```react:src/App.js
// import React, { useState } from 'react';
import React from 'react';

// const initialCount = 0;
function App() {
	/* カスタムフックに切り出す
	const [count, setCount] = useState(initialCount);
	const decrement = () => setCount(count - 1);
	const increment = () => setCount(count + 1);
	*/

}
```

つぎのコード003が、カウンターのカスタムフックのモジュール(`src/useCounter.js`)です。フックの名前は`use`ではじめるお約束になっています。状態が備えられ、フックも使えるのは、関数コンポーネントと同じです。違いとしては、JSXの要素を返さなくて構いません。戻り値は、カウンターの値(`count`)と減算(`decrement()`)・加算(`incfrement()`)の関数を収めたオブジェクトとしました。

#### コード003■カウンターのカスタムフック
```react:src/useCounter.js
import { useState } from 'react';

export const useCounter = (initialCount = 0) => {
	const [count, setCount] = useState(initialCount);
	const decrement = () => setCount(count - 1);
	const increment = () => setCount(count + 1);
	return { count, decrement, increment };
};
```


# カスタムフックを使う
カウンター表示モジュール(`src/CounterDisplay.js`)はカスタムフック(`useCounter`)の呼び出しにより、カウンターの状態を操作するための参照(`count`と`decrement`および`increment`)が得られます。もはや、親コンポーネントからプロパティで受け取らなくてよいのです。

```react:src/CounterDisplay.js
import { useCounter } from "./useCounter";

// const CounterDisplay = ({ counter }) => {
const CounterDisplay = () => {
	const { count, decrement, increment } = useCounter();

}
```

親のアプリケーションモジュール(`src/App.js`)から、子コンポーネント(`CounterDisplay`)に渡していたプロパティ(`counter`)は除いてください。

```react:src/App.js
function App() {
	return (
		<div className="App">
			{/* <CounterDisplay counter={{ count, decrement, increment }} /> */}
			<CounterDisplay />
		</div>
	);
}
```

カウンター表示(`src/CounterDisplay.js`)とアプリケーション(`src/App.js`)モジュールの記述全体は、それぞれつぎのコード004のとおりです。ロジックを切り離したので、ふたつのコンポーネントはともに表示に専念することになりました。作例をCodeSandboxに公開します(サンプル001)。

#### コード004■ロジックを切り離したコンポーネントモジュール
```react:src/CounterDisplay.js
import React from "react";
import { useCounter } from "./useCounter";

const CounterDisplay = () => {
	const { count, decrement, increment } = useCounter();
	return (
		<div>
			<button onClick={decrement}>-</button>
			<span>{count}</span>
			<button onClick={increment}>+</button>
		</div>
	);
}

export default CounterDisplay;
```

```react:src/App.js
import React from 'react';
import CounterDisplay from './CounterDisplay';
import './App.css';

function App() {
	return (
		<div className="App">
			<CounterDisplay />
		</div>
	);
}

export default App;
```

#### サンプル001■カスタムフックを使ったカウンター
[![2009001_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/9038de8e-8cf4-e030-7f93-ef8038d73a65.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-creating-custom-hook-cxps9)