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ファイルをひとつ新たに加えます。
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に以下のように定めます。でき上がるのは、簡単なカウンタです。
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」からお借りしました。戻り値はアロー関数式です。
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にコードを掲げます。
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);

