React: React HooksでcreateReducer()関数を定める

React Hooksには[`useReducer()`](https://reactjs.org/docs/hooks-reference.html#usereducer)という関数が加わりました。状態とアクションのふたつのオブジェクトがあり、アクションに応じて状態を改める仕組みです。簡単なコードを書きながら、具体的な組み立てを確かめましょう。

# Reactのひな形アプリケーションをつくる
Reactアプリケーションのひな形は、つぎのようにコマンドラインツールから`create-react-app`でつくることにします。詳しくは、「[Create a New React App](https://reactjs.org/docs/create-a-new-react-app.html)」をご参照ください。

```terminal
npx create-react-app use-reducer
```

必要なReactのバージョンは「[react Release 16.7.0-alpha.2](https://libraries.io/npm/react/16.7.0-alpha.2)」です。アルファ版ですので、あくまでお試しということでお使いください。`react`に加えて、`react-dom`もインストールします。

```terminal
npm install react@16.7.0-alpha.2
npm install react-dom@16.7.0-alpha.2
```

`package.json`を開けると、つぎのようにReactの依存関係(`dependencies`)が16.7.0-alpha.2になっているはずです。

```json
{

  "dependencies": {
    "react": "^16.7.0-alpha.2",
    "react-dom": "^16.7.0-alpha.2",

  },

}
```

この設定にもとづいて環境を改めます。コマンドラインツールから`npm install`と打ち込んでください。

```terminal
npm install
```

つくられたファイルはつぎの図のとおりです。そのうち4つは要らなくなります。また、あとでJavaScriptファイルをひとつ新たに加えます。

![qiita_12_005_001.png](https://qiita-image-store.s3.amazonaws.com/0/262045/cf50bf29-7342-5951-08f8-cac6b66ee909.png)

Reactアプリケーションをローカルサーバーで開くには、つぎのコマンドを打ち込みます。

```terminal
npm start
```


# useReducer()を使う
`useReducer()`関数の使い方は、公式ドキュメントの「[useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer)」の項に解説されています。その中で紹介されているつぎの関数(`reducer()`)を使いましょう。やっていることは、引数の`action`のプロパティ値(`type`)に応じて`state`のプロパティ値(`count`)が改められ、それを新たな`state`オブジェクトとして返すという処理です。なお、この関数は必ず状態のオブジェクトを返さなければなりません(`default`が必須です)。

```react
const initialState = {count: 0};

function reducer(state, action) {
	switch (action.type) {
		case 'reset':
			return initialState;
		case 'increment':
			return {count: state.count + 1};
		case 'decrement':
			return {count: state.count - 1};
		default:
			return state;
	}
}
```

アプリケーションのページの描画(`ReactDOM.render()`)は、以下の関数(Counter())が担います。引数は状態のオブジェクトです。`useReducer()`メソッドは、上記の関数(reducer())と状態オブジェクトの初期値で初期化します。戻り値は状態オブジェクト(`state`)とアクションを送る関数(`dispatch()`)の配列です。

テンプレートの3つのボタンは、クリックするとそれぞれのアクションのオブジェクトを`dispatch()`メソッドで送ります。すると、今の状態オブジェクトとアクションを、上記の関数が受け取るのです。

```react
function Counter({initialCount}) {
	const [state, dispatch] = useReducer(reducer, {count: initialCount});
	return (
		<div className="App">
			<button onClick={() => dispatch({type: 'reset'})}>Reset</button>
			<button onClick={() => dispatch({type: 'increment'})}>+</button>
			<button onClick={() => dispatch({type: 'decrement'})}>-</button>
			Count: {state.count}
		</div>
	);
}

const rootElement = document.getElementById('root');
ReactDOM.render(<Counter initialCount={0} />, rootElement);
```

これらのコードは`src/index.js`に以下のように定めます。でき上がるのは、簡単なカウンタです。

![1812003_001.png](https://qiita-image-store.s3.amazonaws.com/0/262045/72437a23-bdb6-62a5-9ed5-3a9aa691be0d.png)


```react:src/index.js
import React, { useReducer } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import './App.css';

const initialState = {count: 0};

function reducer(state, action) {
	switch (action.type) {
		case 'reset':
			return initialState;
		case 'increment':
			return {count: state.count + 1};
		case 'decrement':
			return {count: state.count - 1};
		default:
			return state;
	}
}
function Counter({initialCount}) {
	const [state, dispatch] = useReducer(reducer, {count: initialCount});
	return (
		<div className="App">
			<button onClick={() => dispatch({type: 'reset'})}>Reset</button>
			<button onClick={() => dispatch({type: 'increment'})}>+</button>
			<button onClick={() => dispatch({type: 'decrement'})}>-</button>
			Count: {state.count}
		</div>
	);
}

const rootElement = document.getElementById('root');
ReactDOM.render(<Counter initialCount={0} />, rootElement);
```


# createReducer()関数をつくる
つぎに、createReducer()関数をつくりましょう。これは組み込みのメソッドではなく、カスタム関数です。状態とアクションを受け取って処理する関数が返されます。　新たなJavaScriptファイル`src/createReducer.js`に、つぎのように定めます。この関数は、Reduxサイトの「[Generating Reducers](https://redux.js.org/recipes/reducing-boilerplate#generating-reducers)」からお借りしました。戻り値はアロー関数式です。

```react:src/createReducer.js
function createReducer(initialState, handlers) {
	return (state = initialState, action) => {
		if (handlers.hasOwnProperty(action.type)) {
			return handlers[action.type](state, action);
		} else {
			return state;
		}
	};
}

export default createReducer;
```

そのうえで、`src/index.js`の関数(`reducer()`)を置き替えます。条件判定がなくなったことにお気づきでしょう。

```react:
/* function reducer(state, action) {
	switch (action.type) {
		case 'reset':
			return initialState;
		case 'increment':
			return {count: state.count + 1};
		case 'decrement':
			return {count: state.count - 1};
		default:
			return state;
	}
} */
const reducer = createReducer(initialState, {
	reset: () => initialState,
	increment: state => ({ count: state.count + 1 }),
	decrement: state => ({ count: state.count - 1 })
});
```

新たな`src/index.js`のコード全体はつぎのようになりました。ご参考までに、[GitHub](https://github.com/FumioNonaka/react-create-reducer)にコードを掲げます。

```react:src/index.js
import React, { useReducer } from 'react';
import ReactDOM from 'react-dom';
import createReducer from './createReducer';
import './index.css';
import './App.css';

const initialState = {count: 0};
const reducer = createReducer(initialState, {
	reset: () => initialState,
	increment: state => ({ count: state.count + 1 }),
	decrement: state => ({ count: state.count - 1 })
});
function Counter({initialCount}) {
	const [state, dispatch] = useReducer(reducer, {count: initialCount});
	return (
		<div className="App">
			<button onClick={() => dispatch({type: 'reset'})}>Reset</button>
			<button onClick={() => dispatch({type: 'increment'})}>+</button>
			<button onClick={() => dispatch({type: 'decrement'})}>-</button>
			Count: {state.count}
		</div>
	);
}

const rootElement = document.getElementById('root');
ReactDOM.render(<Counter initialCount={0} />, rootElement);
```