1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

gumi Inc.Advent Calendar 2018

Day 12

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

Last updated at Posted at 2018-12-20

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

Reactのひな形アプリケーションをつくる

Reactアプリケーションのひな形は、つぎのようにコマンドラインツールからcreate-react-appでつくることにします。詳しくは、「Create a New React App」をご参照ください。

npx create-react-app use-reducer

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

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になっているはずです。

{

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

  },

}

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

npm install

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

qiita_12_005_001.png

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

npm start

useReducer()を使う

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

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()メソッドで送ります。すると、今の状態オブジェクトとアクションを、上記の関数が受け取るのです。

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

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」からお借りしました。戻り値はアロー関数式です。

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())を置き替えます。条件判定がなくなったことにお気づきでしょう。

/* 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にコードを掲げます。

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);
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?