お仕事で React Redux を使用することになったので、学習した事柄をここにまとめます。
学習の指針
React Redux について理解するためには、まず Redux がどのようなものか調べ、それから React Redux に進むのが吉です。
Redux はそれなりにコンテクストヘビーなパッケージなので、React Redux のドキュメントは Redux を理解している前提で書かれているものがほとんどです。なので、一足飛びに React Redux へ進もうとしても、結局 Redux のドキュメントから読み直す羽目になります。
従って、まずは Redux の公式ドキュメントを読むのがよいです。
以下の順に読むのが React Redux に到達する最短経路だと思います。
- Redux Fundamentals, Part 1: Redux Overview
- Redux Fundamentals, Part 2: Concepts and Data Flow
- Redux Fundamentals, Part 5: UI and React # Using Redux with React
とはいえ、一時的に React Redux の使い方だけを知りたい方もいると思うので、この記事では React Redux の使い方に絞って内容を記述をします。
具体的には、何を解決するために Redux が必要になるのかを簡単に述べ、その後で React Redux を動作させるための最小限のサンプルを示します。
React Redux は何を解決してくれるか
React をはじめ、画面をコンポーネントに分割するライブラリを使っていると、しばしば複数のコンポーネントで状態を共有する必要に迫られます。その際に「その状態はどのコンポーネントで管理するべきか?」という微妙な問題が生じます。
状態を共有すべきコンポーネントがごく近い場所にあるうちは、React が提唱する Lifting State Up といった手法を使うことで、親コンポーネントに状態の管理を委譲できます。しかし、アプリケーションの規模が大きくなってくると、それだけでは対応できないケースも出てきます。
遠く離れたコンポーネント間で状態を共有するナイーブな方法は、深い階層の親子関係を辿って状態のバケツリレーを行うことです。しかし、この方法では中間層のコンポーネントが無駄な状態で汚染されるため、可能であれば別の方法を探りたいところです。
Redux は、上記の問題を「コンポーネントツリーの中で状態管理をするのは無理なので、コンポーネントツリーの外で管理しよう」という思想によって解決するためのライブラリです。
Redux では、コンポーネントツリーのどこからでもアクセス可能な、グローバルな空間の中で状態を一元管理します。そして状態が更新された場合、それを検知する必要があるコンポーネントに対して、更新内容をフィードバックします。
一見、グローバル変数を彷彿とさせるアイデアですが、Redux では、状態を管理する空間に適切な構造を持たせることにより、これを容易に管理可能なものとしています。
結論ですが、React Redux を導入する必要があるかどうかは、ひとえに、
- 画面の複数のコンポーネントから参照される複雑な状態があるかどうか
によって判断するのがよいかと思います。
Redux の構成
Redux は Action・Reducer・Store・Dispatch・Selector という5つの要素から構成されます。
それぞれの役割は以下の通りです。
- Action: 状態更新のトリガーとなるイベントを定義する
- Reducer: 各 Action が発行された際の状態の更新方法を定義する
- Store: Reducer で定義した更新方法に則って状態を一元管理する
- Dispatch: Action を発行する
- Selector: Store から状態を取得する
全体の流れは、公式ドキュメントにある こちらの概念図 がわかりやすいです。
シンプルな構成なので、あとは実際のコードを読めば大体わかります。
各概念の詳細については、他に説明している記事がたくさんあると思うので、この記事では割愛します。
サンプルコード
React Redux を動作させる最小限のサンプルコードを示します。
作成するのは React Redux を使用した簡単なカウンタです。
サンプルコードは Github で公開してあるので、とにかく動かしたい方はこちらをどうぞ。
準備
サンプルコードは create-react-app
で作成されるテンプレートを前提とします。
$ create-react-app react-redux-sample
$ cd react-redux-sample
$ npm install redux react-redux
ついでに必要なファイルも作っておきます。
$ mkdir redux
$ touch redux/action.js
$ touch redux/store.js
Action
Action は type
プロパティを持つ JavaScript のプレーンオブジェクトです。
type
はイベントの種類を表すラベルと考えておけば大丈夫です。
以下に Action の定義を示します。
ここではカウンタのインクリメント・デクリメントの Action を定義しています。
export const ActionType = {
increment: 'increment',
decrement: 'decrement',
};
export const increment = {
type: ActionType.increment,
};
export const decrement = {
type: ActionType.decrement,
};
ここでは type
のみを持つオブジェクトを作成していますが、それ以外のプロパティを持たせることも可能です。追加したプロパティは次に示す Reducer の中で参照可能です。
Reducer / Store
各 Action に対する状態遷移を定義した Reducer と、それを元に状態を管理する Store を作成します。
このサンプルでは、状態はカウンタの値のみを持つオブジェクトです。
Reducer の中でカウンタの値をインクリメント・デクリメントする状態遷移を表現しています。
import { createStore } from 'redux';
import { ActionType } from './action';
const initialState = {
count: 0,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case ActionType.increment:
return { ...state, count: state.count + 1 };
case ActionType.decrement:
return { ...state, count: state.count - 1 };
default:
return state;
}
};
const store = createStore(reducer);
export default store;
React Redux ではコンポーネントからアクセスする Store を指定するのに Provider というタグを使います。Provider タグの store
プロパティに作成した Store を渡すことで、全ての子コンポーネントから、引き渡した Store にアクセスできるようになります。
ここでは index.js に App タグを囲う形で Provider タグを追加します。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './redux/store';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Dispatch / Selector
App.js を書き換えて、Store で管理されているカウンタの値を表示します。また、カウンタの値をインクリメント・デクリメントするためのボタンを追加します。
Store の値を取得するために Selector を使用します。React Redux が提供している useSelector
フックを使用することで、Store の値が更新された際に、自動的にコンポーネントを更新してくれるようになります。
ボタンの onClick
には Dispatch を使用して Action を発行するコールバックを登録します。これにより Reducer 経由で Store の状態が更新されます。Dispatch の取得には useDispatch
フックを使用します。
import './App.css';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './redux/action';
const App = () => {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div className="App">
<p>{count}</p>
<button onClick={e => dispatch(increment)}>Increment</button>
<button onClick={e => dispatch(decrement)}>Decrement</button>
</div>
);
}
export default App;
以上で React Redux を動作させる最小限のサンプルコードができました。
あとは一連の流れを確認して、煮るなり焼くなりすればよいと思います。
おまけ
Redux は React の設計思想である Flux を参考に書かれているそうなので、Flux について知っておくと理解が深まるかもしれません。時間がある時に以下のサイトを読んでおくと良さそうです。