やりたいこと
reduxに依存せずredux-sagaを使う
さらにその上で、
pureなreduxを使う
こちらの記事と方向性は似ていますがredux-sagaまでは捨てられません。
ReduxでのMiddleware不要論
なにがうれしいか
- pureなreduxが使える。reduxはstoreを管理するだけになりシンプル
- reduxに代わるステート管理の機構やフレームワークへの差し替えがきく
どうやるか
redux-sagaにはExternal APIとして、runSaga(options, saga, ...args)というredux-saga以外のコンテキストからsagaを実行するAPIが存在することは以外と知られていないかもしれません。以下を参照
https://redux-saga.js.org/docs/api/
これを使えば、redux middlewareにredux-sagaをインストールすることなく任意のsagaを実行できます。
まずは、以下のようなrunSaga()のラッパーを書きます。
const sagaOptionSingleton = (() => {
let channelStd = stdChannel();
let emitter = new EventEmitter();
emitter.on("action",channelStd.put);
return {
channel:channelStd,
dispatch:output => {
emitter.emit("action",output);
},
getState:() => {}
};
})();
export function runSagaWithFixedOpt(saga){
runSaga(sagaOptionSingleton,saga);
}
オプションにstdChannelのインスタンスを設定しているところが重要です。
公式ドキュメントではあまり説明されていませんが、
stdChannelはsagaのコンテキストで何らかのデータがyield putされた時sagaのaction channelにバッファするためのインターフェースです。これはシングルトンでなければいけません。
そのため、self executing functionでsagaOptionSingletonを生成し、runSagaのオプションは常にこれを使います。
このように、reduxのミドルウェアではなくなるため、当然ながらこのラッパーを使って実行するsagaやそこからフォークされるsagaの内部でyield putをしても、reduxのstoreには影響が及びません。
storeにデータを書き込みたければ普通にstore.dispatch()を使うだけです。
これでpureなreduxを使うことができるようになりました。
では、react-reduxのmapDispatchToPropsからdispatchされたアクションをsagaはどのように検知するか。
上記と同じ理由で検知できないので、
代わりに上記で書いたヘルパーを更にラップします。
export function sendToSaga(data){
function* sendSomethingToSaga(){
yield put(data);
}
runSagaWithFixedOpt(sendSomethingToSaga);
}
つまり非sagaコンテキストから単発のsagaを起動し、putを実行することでsagaへイベントを送ります。
すると、mapDispatchToPropsの実装は以下のようになります。
const mapDispatchToProps = dispatch => {
return {
increment: () => {
sendToSaga({ type: 'INCREMENT' });//sagaへイベントを送る
}
}
}
このようにdispatchを使わなくなりました。(別に使っても良い)
結論
reduxに依存することなくredux-sagaを使うことができるようになりました。