React

Redux 学習の軌跡と自作サンプルの解説

Redux Study

DEMO

対象

React の基礎を身につけた上で Redux に入門する人向けです。

範囲

公式の Basics · Redux で紹介されている範囲で理解を深めます。 幸い、これを翻訳してくださった方がいらっしゃるので、それを読んで学習します。

公式

読んだ記事

概念を理解する

基礎を身につける

例を見て体験する

参考

役に立つ図

Redux Structure Reactive State Machine (Japanese) // Speaker Deck

Introduction to Redux // Speaker Deck

実際に作ってみる

いろいろな記事を読んでイマイチよくわからなかった部分がありました。 上に挙げた図のように Actions や Reducers という概念は出てきますが、 ソースコード上で一体どれを指しているのか迷う ことがあって、何度も読み直しました。

そこで 「自分だったらこう書いてあったらわかりやすい」 を目指して実際に作ってみることにしました。

サンプルの題材と作成方針

公式のサンプルにあるカウンターよりも詳細、かつ Todo アプリよりも簡素なものを考えました。 そこで思いついたのが RGB の3色を制御することでした。上述のような迷いを解決するために、 以下の目的を達成することを念頭に置いてコードを書きました。

構成

開発の土台は、公式のサンプルを流用するのが手っ取り早いです。 今回はカウンターアプリを拡張していくことにしました。

Redux Study Workspace

実際に作ってみてわかったこと

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 を扱いたいと考えています。 しかし、参考記事に挙げたような懐疑的な声もあります。銀の弾丸はありませんので、規模や目的に 合ったものを選択していきましょう。実際にその手で試してから、ね。