Reduxとは?
この記事はQiita React.js Advent Calendar 2015の4日目に投稿させて頂いた記事です。
この記事でわかること
- Reduxの基本的概念とRedux関連の用語
背景
Reactで良く使われるアーキテクチャスタイルにFluxというものがある。これはFacebookが考案したアーキテクチャスタイルで、MVCの亜種にオブザーバパターンを乗せてデータの一方向性のルールを適用させたものだ。このFluxから派生したアーキテクチャスタイルであるReduxが海外で評判が良く、自分でも使って見たくなったので色々調べてみた。
前提
Reactの基本的なことは理解している。
Reduxとは?
ReduxはFluxを派生させたアーキテクチャスタイルであり、このアーキテクチャに則ったアプリケーションは①ステート[1]の管理が容易、②異なる開発環境(client,server, native)で一貫した振る舞いを持つアプリケーションの開発が可能、③テストが容易、④ステートの変更を遡れるデバッガーなど便利なツールを使った開発が可能、⑤Reactとの相性が良いなどの恩恵が得られる。
最近流行りのSPAなんかはアプリケーションのステート管理が複雑化しており、ステートをいかに簡単に管理できるかが課題になっている。ReduxはJavaScriptアプリケーションのステートの変更の手段とタイミングに制約を設けることでステートを予測可能にし、その管理を容易にする。
また、Reduxに則って開発するアプリケーションの関数群はステートレスなためテストが容易になる。Single Source of Truthの原則に従っているため、ステート変更のUndo、Redoが容易にでき、gaearon/redux-devtools · GitHubを使えばhot loadingやアプリケーションのステートの推移をコントロールできるため開発が非常に容易になる。次のビデオはHot Reloading with Time Travelというreact-europe 2015で発表されたでモンストーレションである。
Reactの課題である、ステートの管理を実現するアーキテクチャなのでReactをViewで使うアプリ開発者に特に注目を浴びている。
実装としてのRedux
先ほどReduxはアーキテクチャスタイルと述べたが、同じ名前のフレームワークも公開されている。このフレームワークを利用することで簡単にReduxアーキテクチャを自分のアプリケーションに取り込める。
データフロー
ReduxはFluxと同様、データの流れを一方向にする規約が有る。この規約はStoreのステートツリーを更新するときは必ずActionを経由する必要があるというものだ。Action経由でのStoreの更新は具体的には以下のような流れになる。
- Action CreatorがViewやスケジューリングされている非同期のイベントなどをトリガーにActionを生成する(①)
- Middlewareがステート変更前後で任意の処理を実行する(②、③)
- Reducerが変更後のステートStoreに知らせる(④)
- Storeがステートを更新した後、Viewが変更を検知し自らを更新する(⑤)
Action
は文字列定数の識別子(type)とStoreへの入力情報となるデータから構成されているJavaScriptオブジェクトのこと。
{ type: ADD_TODO, text }
Storeが管理するステートツリーはActionを経由しなければ更新することができない。また、Actionは何が実行されるのかという事実を扱う。ステートツリーをどのように変更するかを定義するのはReducerの担当になる。
Action Creator
は文字通りActionを生成するステートレスな関数(つまり、入力が同じであれば1000回実行しても同じ出力が1000回返される)。
function addTodo(text) {
return { type: ADD_TODO, text };
}
Reducer
[2]は現在のステートと実行するActionを受け取り、変更したステートを返す関数。この関数もステートレスに実装する必要がある。
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, {
text: action.text,
completed: false
}];
case COMPLETE_TODO:
return [
...state.slice(0, action.index),
Object.assign({}, state[action.index], {
completed: true
}),
...state.slice(action.index + 1)
];
default:
return state;
}
}
また、combineReducers()を使えばReducerの分割が容易にできる。
const todoApp = combineReducers({
visibilityFilter,
todos
});
export default todoApp;
Reducerでステートレスな関数(pure functionと表現されていた)にする必要があるため、以下のようなことは絶対にしてはいけない。
- Reducer内でのデータの変更
- API呼び出しやルーティングトランジッションの実行
- Date.now()やMath.random()のような値が毎回変わる関数を呼ぶこと
Store
はアプリケーションのステートツリーを保持するもの。Fluxでも同様の概念があったが、ReduxではStoreは1つしか存在しない(シングルトン)。データ操作のロジックを分割したい場合はreducer composition(Reducerの分割)を使い複数Storeの利用は避けなければならない。
Action、Action Creator、Reducer、StoreはRedux用語であるが、Reducer以外はFluxでも使われるもの。一方でFluxで登場していたDispatcherはReduxでは使われない。
3つの原則
Stateの管理を容易にするためにReduxでは以下の3つの原則を守る必要がある。
Single Source of Truth
アプリケーションのステート管理元は1つである必要がある。これは、Reduxを使ったアプリケーションは1つしかStoreを持たないことを指す。Fluxは複数のStoreを持つことが出来たので混同しないこと。
State is read-only
ステートは閲覧のみが許され、更新するときは必ずActionを経由する必要がある。
Mutations are written as pure functions
ステートの変更を行うReducerはステートレスな関数でなければならない。
所感
まだReduxを使って大きなアプリは開発してないから何とも言えないところはあるけど、とてもシンプルなフレームワークなのでラーニングコストもそこまで高くないんじゃないかな[3]。また、ReduxはES6の記法をガンガン使って実装されているのでES6に慣れたいという人も使っているうちに書けるようになってくるので一石二鳥感がある。
おまけ:FluxとReduxの関係
Reduxの源流であるFluxについて知りたい人はこの辺を読んで見てください。Reduxだけ理解したい場合はFluxを知らなくても大丈夫です。
僕はFlux自体は新しい概念だと考えておらず、Fluxとはなんだったのか + misc at 2014 - snyk_s logのエントリーと同じ意見だ。ポイントはFacebookというブランド力のある会社がデータ一方向性の概念にFluxと命名したことに尽きると思う。
Fluxの実装は色々あるみたいでその中で実績のある物を探していたのだが、Reduxが海外で圧倒的に人気が出ているようだった。Flux実装の1つのFluxmmoxのGithubレポジトリを覗いてみると、
4.0 will likely be the last major release. Use Redux instead. It’s really great.
とまで言わしめている程だ。また、Facebookの中の人もReduxを賞賛しているようだ。
@dan_abramov BTW, I asked for comments on Redux in FB's internal JS discussion group, and it was universally praised. Really awesome work.
— Bill Fisher (@fisherwebdev) 2015, 7月 1
と、そんなこんなで興味が湧いたのが今回の記事に繋がっている。
参考
Reduxについて
色々な人の所感
- 人気のFluxフレームワークReduxをさわってみた - マルシテイアは月の上
- Reduxの設計で気をつけるところ - なっく日報
- fluxフレームワークreduxについてドキュメントを読んだメモ - fukajun
最新情報の収集リソース
[1] なお、Reduxに関する文書を読んでいるとステートもしくはステートツリーという単語が頻繁に出て来るが、これはRedux Storeが管理するステートツリーのことである。Reduxを使ったアプリケーションでは必ずステートツリーは1つになる。
[2] Reducerの語源はES6で新しく追加されたArray.prototype.reduce()関数で、この関数の引数にコールバックとして利用するためだそうです。ただ、ES6のreduceは関数型プログラミングとしての設計としてはいけていない模様。
追記
ES5からある関数でした(参考)。
[3] フレームワークとかツールってすぐに新しいものが出てくるので、あまりそれの習得に時間を割くのは利口なことではないとは思っている。