Edited at

redux-aggregate v2 - Action / Reducer の分割 -

More than 1 year has passed since last update.

redux-aggregate が v2 になりました。主な変更点は以下の通りで、v1 からの Breaking Change はありません。アプリケーションスケールアップの際に効果が期待出来る変更を追加しています。


  • createActions の追加

  • Aggregate.subscribe の追加

  • subscriptions 概念の追加

これからは createAggregate で生成された ボイラープレートでも、reducer / action が多対多の関係を持つことが可能になります。もちろん新しく追加されたAPIも、必要最小限の型定義から推論型を導出します。


createActions

従来の ActionCreator 定義を確認してみましょう。ActionType と payload が結合していますね。


actions/timer.js

function tick (message) {

const date = new Date()
if (message !== undefined) return { type: 'TIMER_TICK', pyload: { date }}
return { type: 'TIMER_TICK', payload: { date, message }}
}

ここから ActionType を取り除き、payload だけを return するものを actionSrc と呼んでいます。in/out があるだけのただの純関数であることが分かります


actions/timer.js

// @ ActionSources

function tick (message) {
const date = new Date()
if (message !== undefined) return { date }
return { date, message }
}
export const TimerAC = { tick }


createAggregate と同様に、ActionSources / namespace を付与し createActions を call します。


store.js

import { createActions } from 'redux-aggregate'

import { ActionSources } from 'path/to/actions/timer'

const Timer = createActions(ActionSources, 'timer/')
const {
types, // ActionTypes
creators, // ActionCreators
} = Timer


createActions では reducerFactory は生成されません。


Aggregate.subscribe

createAggregate で生成された Aggregate インスタンスに、subscribe というメソッドが生えました。このメソッドで上述の Actions を購読します。購読に応じた変更処理は後述の subscriptions に書いていきます。


store.js

import { TimerActions } from 'path/to/actions/timer'

import { TodosMutations, TodosSubscriptions } from 'path/to/models/todos'
export const Timer = createActions(TimerActions, 'timer/')
export const Todos = createAggregate(TodosMutations, 'todos/')

Todos.subscribe(
Timer, // Actions or Aggregate
TodosSubscriptions.Timer // Subscriptions
)


一般的な subscribe とは少し異なり、これは reducerFactory を拡張するものです(うっかり多重 subscribe したところで、違いはありません)。


subscriptions

mutations が定義されているモデルファイルに、購読マップ(subscriptions)を定義します。見た目は mutations とそっくりですが、ActionType / ActionCreator を生成しません。subscription 単体は、従来の switch文で選り分けられた先の reducer と等価です。

注意しなければいけないのは、subscription は規約を守らなければ、型エラーが発生します。一体何の規約かというと、それは開発者自身が定義したメソッド名・payload に則るということです。

example で示しているとおり、TimerActions の tick というメソッド名・メソッド戻り値を第2引数にとることが subscription関数に期待されます。第1引数は TodosState を指します。modelファイルに定義される全関数は、所属する state を中心に回ります。


actions/timer.js

// subscription の名称は tick に制約される

function tick (message) {
const date = new Date()
if (message !== undefined) return { date }
return { date, message }
}
// subscription の第二引数は { date } か { date, message } に制約される


models/todos.js

const TodosSubscriptions = {

Timer: {
tick(state, { date, message }) {
if (message !== undefined) {
return { ...state, date, messageFromTimer: message }
}
return { ...state, date }
}
}
}

他のメディアでも発信していますが、このライブラリの最大の利点は、「アプリケーションの核が純粋な言語仕様のみで確立されていること・ Redux の概念が介入していないこと」であり、コード・付随テストの寿命を伸ばす大事なことだと筆者は考えています。


利用シーン

概念が増えたことで少しややこしくなりましたが、今回の追加機能は必要に応じて導入、スケールアップのシーンで役立てて下さい。はじめは 単体Aggregte の関心事であった Action が、他のAggregate の関心事となったタイミングで利用すると良いかと思います。

Aggregate.subscribe は 他Aggregate の Action も購読出来ます。2つのAggregate の関心ごとになった際には、下流が上流を購読すれば、それで十分です。それより多くの関心事となった場合や、当初から多くの関心事であると想定された場合は、createActions を使って「Action のみの集約」を作ってください。

Action を全く持つ必要のない集約の場合、createSubscriberを使って「Reducer のみの集約」を作ってください。example ->

公式Document を公開しました https://redux-aggregate.js.org/