Reducer はなぜ reduce なのか
#Redux を初めたときに、reducer の設計がうまくできない、わからないという声を聞くことがあるが、 これはそもそも reducer という概念がわかり易くないことに起因していると思っている。
最近はそもそも redux-toolkit などがあるので概念的に理解する必要性も薄くなっているのだが、とはいえ説明した方が良いこともある。
たとえば初心者に次の質問をされたときに、その意味に即した説明ができるだろうか
なぜ reducer で副作用を起こしてはいけないのか
なぜ Array.prototype.reduce と同じ型で表現するのか
前者について、「タイムトラベルデバッグが壊れる」といった具体的な実例をあげて答えることはできるが、
そうするとタイムトラベルデバッグをしない人は副作用を起こして良いことになるので、あまり良い説明ではないと思う。
ちなみに自分なら次のように説明する
reducer は処理ではなく 法則を記述した式であるから
ちょっと違うけど数列の漸化式とか、そういうのに近い
式の記述の中に副作用が出現するのは、実害とか関係なくおかしい
「初期状態と時刻 t における出来事から時刻 t + 1 における状態を求める」のが Redux の世界観であるから
これは時間の流れを離散的な数列のように表現することを意味する
初期状態があって、そこからの積み重ねと1つ前の項から次の項を求めるというのは畳み込み演算(fold / reduce)と等価
reducer は Array.prototype.reduce を配列の代わりに出来事(action)の流れに対して適用したものと見なせる
図で表すとこんな感じ。
code:txt
↓ ↓
↓ ↓
↓ ↓
↓ ↓
↓ ↓
↓ ↓
それぞれに Redux の言葉を当てはめるとこうなる。
code:txt
action state
------- -----
↓ ↓
この図で 0 と up! から 1 に伸びる 2 本の矢印があるが、この 2 本の矢印を関数で表現したのが reducer。
code:js
function reducer(currentState = initialState, action) { return nextState }
そして、時刻 t + 1 に新しい action を追加する操作が dispatch。
code:diff
↓ ↓
+ ↓
dispatch をすると、reducer が自動で次の state を計算する。
code:diff
↓ ↓
+ ↓ ↓
こういう設計なので、原理的にはデバッグ時に時刻 2 に戻って当時の状態を復元みたいなことができるはずであり、それがタイムトラベルデバッグ( redux-devtools にその機能がある )。
実際にしたいかはともかく、できる設計になってないなら何かが間違っているというのが Redux の主張。
また Redux のベストプラクティスとして action の type 名に setCount のような名前をつけるなというのがあるが、
これは set〇〇 が「出来事」ではなく「処理」っぽい名前になっているから(分かっててやってるなら良いが、初心者メンバーの誤解を助長しがちなので推奨されない)。
なのでそれよりも、たとえば down とか count/decremented の方が命名として望ましいということになる。
以上。