Edited at
gumi Inc.Day 12

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

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);