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