ReduxでFluxなアプリを実装する(1)
ReduxはFluxアーキテクチャを実現するためのライブラリのひとつです。Fluxで規定されている単方向データフローを実現しやすいようにDispatcherやStoreの仕組みを提供しているだけではなく、Store内のデータ管理をしやすくなる仕組みであるReducerや、機能を拡張するためのMiddlewareの仕組みが用意されていることが大きな特徴です。これらの概念を含めて図示したものが図3です。
この図の中で最も重要なのはReducerです。Reducer自体は「Actionを受け取って、Actionの内容に見合った新しい状態を生み出して返却する」といった機能を持った関数です。StoreはReducerの単位で状態を管理しており、ReactなどのViewに状態を渡す際には、Reducerの単位で渡すのが基本になります。アプリケーションの中で発行されたActionは、すべてのReducerに渡されるので、Reducer側は自分が管理したいデータに関係のあるActionだけを見繕って処理・保存していくことになります。
MiddlewareはActionが発行されてからReducerに渡されるまでに動く関数で、Actionを加工したり、副作用を起こしたり、なんでもアリの便利屋さんです。初心者のうちはMiddlewareを自作する機会はほぼないと思いますが、次回紹介する予定の「redux-thunk」など、有用なツールがMiddlewareの形で提供されていることが多いということは覚えておいてください。
サンプルの仕様について
本稿ではReduxのサンプルとしてよく取り上げられる、カウンターアプリを作成します。
カウンターアプリは次の3つの要素から構成されます。
- カウント表示部
- 「+1」ボタン
- 「-1」ボタン
「+1」ボタンを押すとカウント表示部の数字が加算され、「-1」ボタンを押すと減算される、というシンプルな仕様です。内部的には「現在のカウント数」という状態を扱うことになり、「+1」と「-1」にそれぞれ状態の増減を表すActionが割り当てられることになります。
Reduxをインストールする
まずは、create-react-appで作成したプロジェクトにReduxをインストールします。次のコマンドを実行してください。
$ npm install --save redux $ npm install --save react-redux
reduxとreact-redux、ふたつのパッケージをインストールしました。ReduxはReactと相性の良いライブラリですが、Reactのためだけに作られたライブラリではありません。そのため、ReduxはReactと接続するための機能を持っていないのです。そんな事情から、Reactと併用する場合には、react-reduxをインストールする必要があります。
package.jsonを開くと、dependenciesの中にreduxとreact-reduxが増えていることが確認できます。
"dependencies": {
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-redux": "^5.0.7",
"react-scripts": "1.0.16",
"redux": "^3.7.2"
},
カウンターアプリのUIを仮実装する
Reduxの実装を始める前に、まずは一度、React側だけで動くサンプルを作ってみましょう。App.jsを次の通り実装します。
import React, { Component } from 'react';
export default class App extends Component {
/** コンストラクタ。stateの初期化もここで行います */
constructor(props) {
super(props);
this.state = {
count: 0 // (1)
};
}
/** +1ボタンを押した際にstateを更新する処理 */
_handlePlusOneClick(e) {
this.setState({
count: this.state.count + 1 // (2)
});
}
/** -1ボタンを押した際にstateを更新する処理 */
_handleMinusOneClick(e) {
this.setState({
count: this.state.count - 1 // (3)
});
}
render() {
const count = this.state.count;
return (
<div>
<button onClick={e => this._handleMinusOneClick(e)}>-1</button>
<span>{count}</span>
<button onClick={e => this._handlePlusOneClick(e)}>+1</button>
</div>
);
}
}
この例では、(1)のようにAppコンポーネントのstateに状態(=カウント)を置いています。ユーザーが「-1」や「+1」のボタンを押した際には、(2)や(3)の通りsetStateで状態を増減させます。Reactだけでやりたかったことが実現できてしまいました。実は、この規模であればReduxがなくても実装できてしまうのです。
わざわざReduxを使う理由
では、余計なツールを使わずにReactだけですべての状態管理をすればいいのか、というとそうではありません。実際に作るアプリには、ふたつどころではない多くのボタンが付いています。テキストを入力することもあるかもしれません。ボタンを押したのは何回目なのか、いま何文字入力しているのかを、状態として保持しなければいけないかもしれません。さまざまなユーザーイベントがコンポーネント内を飛び交い、それぞれのイベントに応じてどんな状態変更を行うのかを長々と記述することもあるでしょう。
このようにアプリケーションの規模が大きくなっていくにつれて、Reactコンポーネントの見通しが悪くなっていきます。JavaScriptの中で手軽にレイアウトを構築できることがReactの大きな利点だったはずなのに、これでは本末転倒です。
そういった状態管理を、ReduxのようなFluxアーキテクチャに切り出すことで、Reactコンポーネントが見た目の構築に注力できるのです。
