ReduxはJavaScriptのアプリケーションの状態を管理しやすくするためのフレームワークです。
FacebookがFluxというアーキテクチャを提唱していて、それをもとに作られたものがReduxだそうです。
Fluxとは
FluxはフロントエンドのWebアプリケーションをつくるためのアーキテクチャです。
データの流れを1方向としていて見通しのよいアプリケーションをつくることができます。
このアーキテクチャでは3つのコンポーネントが登場します。
- Dispatcher 状態を更新するための命令(Action)をStoreに伝えます
- Store アプリケーションの状態を一元的に管理します
- View Reactコンポーネントです
Reduxとは
ReduxではStoreを管理するためのコンポーネントとしてReducerというものがあります。
- Reducer ある状態とActionを受け取って、新しい状態を決定します
Counterアプリをつくる
つくったほうが理解が進みます。とりあえずつくってみましょう。
アプリケーションの作成とインストール
1 2 3 4
| create-react-app react-counter cd react-counter npm install redux react-redux npm start
|
コード
まずコードを載せます。
src/App.jsを以下のように変更して、http://localhost:3000 を開いてみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| import React from 'react'; import { connect, Provider } from 'react-redux'; import { createStore } from 'redux'; import './App.css';
// Reducer const counter = (state = 0, action) => { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } };
// Counter component const Counter = props => { const count = props.count; const increment = props.increment; const decrement = props.decrement; return ( <div> Count: {count} <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); };
const mapStateToProps = state => ({ count: state, });
const mapDispatchToProps = dispatch => { return { increment: () => dispatch({ type: 'INCREMENT' }), decrement: () => dispatch({ type: 'DECREMENT' }), }; };
const CounterConnect = connect(mapStateToProps, mapDispatchToProps)(Counter);
// App component const store = createStore(counter); const App = () => { return ( <Provider store={store}> <CounterConnect /> </Provider> ); };
export default App;
|
IncrementボタンやDecrementボタンをクリックすることでCountの値が変わりましたか?
では、解説に移ります。
Reducer
Reducerは有限状態機械です。初期状態があり、受け取ったイベントによって別の状態へと遷移します。もちろん、遷移しないこともあります。
Reducerではイベントをアクションと呼ぶようです。
以下に今回のReducerを抜粋します。
1 2 3 4 5 6 7 8 9 10
| const counter = (state = 0, action) => { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } };
|
このReducerの状態は単なる数値です。引数は、初期状態とアクションです。
INCREMENT
を受け取ったら状態の値に1加算し、DECREMENT
を受け取ったら状態の値から1減算します。
戻り値は新しい状態の値です。
Counter component
続いてカウンターコンポーネントです。このコンポーネントは現在のカウント値、Incrementボタン、Decrementボタンを含みます。
しかし、現在のカウント値はStoreにあるため、何かしらの方法でStoreの値を参照する必要があります。
また、何かしらの方法でReducerに対してアクションを伝える必要があります。
このReactコンポーネントとStore、Reducerをつなぐためにconnectを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const Counter = props => { const count = props.count; const increment = props.increment; const decrement = props.decrement; return ( <div> Count: {count} <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); };
const mapStateToProps = state => ({ count: state, });
const mapDispatchToProps = dispatch => { return { increment: () => dispatch({ type: 'INCREMENT' }), decrement: () => dispatch({ type: 'DECREMENT' }), }; };
const CounterConnect = connect(mapStateToProps, mapDispatchToProps)(Counter);
|
connectの引数は2つあります。
mapStateToPropsは、どのStateの、どのプロパティをReactコンポーネントに渡すかを決めます。
今回Stateは1つしかなくプロパティもない値そのものなので、Stateをそのまま渡します。名前はcount
です。
Counterコンポーネントでは{count}
として表示しています。
mapDispatchToPropsは、どんなActionをReactコンポーネントに渡すかを決めます。
increment
関数は{ type: 'INCREMENT' }
をdispatchします。
CounterコンポーネントではonClick
でincrement
が呼び出されるようにしています。
ボタンクリック時の流れ
では、Incrementボタンがクリックされたときの流れを説明します。
Increment
ボタンがクリックされるとonClick
に定義されているincrement
関数が呼び出されます。
increment
関数はconnect
経由で渡ってきた関数です。これを呼び出すとcounter
Reducerが呼び出されます。
case 'INCREMENT':
の条件を満たし、state
の値が1増えます。
state
の値が変化したのでCounter
コンポーネントの再描画が行われます。
- その結果、画面上のcountの値が更新されます。
別の書き方
今までのコードの別の書き方を紹介します。
JavaScriptのいろいろな書き方をある程度知っておくと他のコードが読みやすくなります。
分割代入
1 2 3
| const count = props.count; const increment = props.increment; const decrement = props.decrement;
|
これは、ES2015の分割代入を利用して以下のように書けます。
1
| const { count, increment, decrement } = props;
|
この方法を使って、Counter
の引数の部分を書き直すこともできます。
1 2
| const Counter = props => { const { count, increment, decrement } = props;
|
書き直すと以下のようになります。
1
| const Counter = ({ count, increment, decrement }) => {
|
mapDispatchToProps
mapDispatchToProps
を別の書き方で書いてみます。
mapDispatchToPropsの2つ目の引数は、関数ではなくオブジェクトを渡すこともできます。このオブジェクトの要素は関数です。
オブジェクトを渡した場合、各要素の関数をdispatchでくるんでくれます。では、実際に書き直します。
1 2 3 4 5 6
| const mapDispatchToProps = dispatch => { return { increment: () => dispatch({ type: 'INCREMENT' }), decrement: () => dispatch({ type: 'DECREMENT' }), }; };
|
以下のようになります。
1 2 3 4 5 6 7 8 9
| const increment = () => ({ type: 'INCREMENT', });
const decrement = () => ({ type: 'DECREMENT', });
const mapDispatchToProps = { increment, decrement };
|
コード量が増えてますが、increment
とdecrement
の関数はdispatch
に依存しない純粋な関数になりました。