DEMO
対象
React の基礎を身につけた上で Redux に入門する人向けです。
範囲
公式の Basics · Redux で紹介されている範囲で理解を深めます。 幸い、これを翻訳してくださった方がいらっしゃるので、それを読んで学習します。
- Actions
- Reducers
- Store
- Data Flow
- Usage with React
公式
読んだ記事
概念を理解する
基礎を身につける
例を見て体験する
参考
- Coffee, jQueryで書いていたElectronアプリをES6, React, Reduxで書き直した - k0kubun’s blog
- mizchi の Redux 考 - Togetterまとめ
- なぜReduxを使うのか // Speaker Deck
- Clean Architecture in React – Medium
役に立つ図
実際に作ってみる
いろいろな記事を読んでイマイチよくわからなかった部分がありました。 上に挙げた図のように Actions や Reducers という概念は出てきますが、 ソースコード上で一体どれを指しているのか迷う ことがあって、何度も読み直しました。
そこで 「自分だったらこう書いてあったらわかりやすい」 を目指して実際に作ってみることにしました。
サンプルの題材と作成方針
公式のサンプルにあるカウンターよりも詳細、かつ Todo アプリよりも簡素なものを考えました。 そこで思いついたのが RGB の3色を制御することでした。上述のような迷いを解決するために、 以下の目的を達成することを念頭に置いてコードを書きました。
- Action とか ActionCreator がよくわからない
- State が複数ある場合はどうするのか
- State がオブジェクトのときどうするのか
- 1つのアクションが複数の State に作用するときどうするのか
- 各ソースコードファイルの役割の明確化
構成
開発の土台は、公式のサンプルを流用するのが手っ取り早いです。 今回はカウンターアプリを拡張していくことにしました。
- 元にした公式のサンプル(カウンター)
- 自作したサンプルのリポジトリ
実際に作ってみてわかったこと
Action / ActionCreator は、出来上がったアクションを返すのか(Action)、引数をもとに 内部で判断してアクションを導き出すもの(ActionCreator)という理解に至りました。 呼び出し側で事前にアクションが決まっている場合は前者、状況に応じてアクションを変えたい場合に後者 という使い分けをするのだと思います。
actions/index.js
// Action
const hitReset = { type: 'RESET' }
// ActionCreator
const hitR = (isPlus = true) => {
if (isPlus) {
return { type: 'HIT_INCR_R' }
} else {
return { type: 'HIT_DECR_R' }
}
}
また、今回のサンプルでは R+ など色に変化を与えるボタンを押すと、色とカウンターの両方が
作用するようになっています。この挙動の実現に当初は dispatch
を2度呼んで Color と
Count の両方にアクションを伝えていたのですが、ColorReducer 内でプリントデバッグしていた際に
Count に対するアクションも流れてくることに気づきました。
つまり、アクションはすべての Reducer に対して伝えられる のですね。
ということで、カウンターを増加させるアクションすべてをリネームし HIT_INCR_R
のように
HIT を先頭につけ、正規表現でそれを拾い上げてカウントアップするようにしてみたところ
これがうまくいきました。どことなくアンチパターン臭がしますが、そういう仕組みなんだよと示唆する
ためにも今回はよしとしましょう。
reducers/CountReducer.js
const count = (state = initialState, action) => {
switch (true) {
case /^HIT/.test(action.type):
return state + 1
case /RESET/.test(action.type):
return initialState
default:
return state
}
}
ここまでを抑えた上で最後に実装してみたのが RESET ボタンでした。
{ type: 'RESET' }
を Store に Dispatch し、Color と Count の両方が反応するように
どちらの Reducer にもそれを待ち構える形で記述します。
reducers/ColorReducer.js
const color = (state = initialState, action) => {
switch (action.type) {
case 'HIT_INCR_R':
return Object.assign({}, state, {
r: state.r >= 255 ? 0 : state.r + 15
})
/* 中略 */
case 'RESET':
return initialState
default:
return state
}
}
おわりに
次は Advanced な領域に踏み込んで、非同期処理や Middleware を扱いたいと考えています。 しかし、参考記事に挙げたような懐疑的な声もあります。銀の弾丸はありませんので、規模や目的に 合ったものを選択していきましょう。実際にその手で試してから、ね。