社内勉強会メモ
概要
Reduxはアプリケーションの状態管理のためのフレームワークだが、非同期処理についてはなんらサポートを提供するものではなく、その扱いは開発者の手に委ねられている。
ネット上に転がっている、React + Reduxの非同期に関連する手法。
- コンポーネント上で行う(ライブラリを何も使わない)
-
redux-promise
- アクションとして(プレーンオブジェクトではなく)Promiseオブジェクトを渡せるようにする。
- つまり、コンポーネント(もしくはアクションクリエータ)で非同期処理を実行する。
- = 非同期処理の結果をそのままアクションとしてdispatchできる。
-
redux-thunk
- アクションとして(プレーンオブジェクトではなく)関数を渡せるようにする。
- つまり、middlewareで非同期処理を実行する。
-
redux-saga
- タスクという独自の概念をReduxに導入する。
- その中で色々できる。
- actionのdispatch
- 非同期処理
- saga自体は、非同期処理のためのライブラリというわけではなく、非同期も上手く処理できるsagaタスクという概念を実装するライブラリ。
-
redux-api-middleware
- RSAAという独自のアクションをdispatchすると、middleware内で非同期処理(というよりAPIコール処理)を実行してくれる。
- つまり、middlewareで非同期処理を実行する。
- middlewareを使わずに独自の概念で実装する。
コンポーネント上で行う。
特にライブラリを使わずに、コンポーネントにそのまま書く。
React.useEffectで非同期処理をする場合の注意点2つ
結果のデータ、誰がどう持つ?
二つの選択肢があって、
1. そのコンポーネントのReact Stateに保存する。
非同期処理なので、render関数は先にコンポーネントを返してしまう。
つまり非同期処理が結果を受け取ってから表示を更新する必要がある。
コンポーネントが自身を変更するためには、React Stateを使う。
それ(個々のコンポーネントで色んなデータをいじくり回す)をやりたくないからReduxを導入してるので却下。
2. アクションを発行してRedux Stateに保存する。
非同期処理の結果を受け取ったらdispatchすれば良い。
// 任意の時間待つ関数
const wait = (milliseconds: number) =>
new Promise(resolve => {
setTimeout(() => resolve(), milliseconds);
});
const Component = (props: Props) => {
React.useEffect(() => {
// useEffectの第一引数は返り値がcleanup関数のためasyncにできない
(async () => {
console.log("start!");
await wait(3000);
console.log("waited for 3000 milliseconds!");
const result = await callAPI();
console.log(result.hoges);
// dispatchする関数
// props.hogesにmapされる
props.setResult(result.hoges);
})();
}, []);
return (
<div className={styles.wrapper}>
<TasksComponent tasks={props.hoges} />
</div>
);
};
まあできる。
ただしこのやり方、Storybookだと致命的。
Storyを開いた瞬間に必ずAPIリクエストが発火するのでツラい。
Storyの時は発火しないみたいな処理を入れるのもツラい。
そういう意味ではactionをdispatchするだけにした方がやはりテスタビリティは上がる。
前提知識:Reduxのmiddleware
dispatchを改造(というよりdispatchに新しい処理を追加)するもの。
実態は一つの関数で、下記のインターフェースを持つ関数をmiddlewareと呼ぶ。
const myMiddlware = ({ getState, dispatch }) => next => action => {
next(action)
};
その証拠に、redux-promiseもredux-thunkも、ライブラリ本体のコードはindex.jsだけの1ファイルで、20行以下。
Redux Middleware の仕組みと作り方
redux-thunkを学ぶ
Reduxのmiddlewareを積極的に使っていく - Qiita
Redux Middleware in Depth - Qiita
redux-promise
reduxのactionは本来プレーンなオブジェクトである必要がある。
https://github.com/reduxjs/redux/blob/3f93d6bb21fd104263bc83da87bd2e113e82bd9f/src/utils/isPlainObject.js
このisPlainObjectっていう関数で判別してる。
redux-promiseは、アクションとしてPromiseオブジェクトを渡すことができるようになる。
渡されたPromiseがresolveされたものを実際にdispatchする。
つまり、非同期処理自体はaction(Promiseオブジェクト)をdispatchする前に実行し、そのPromiseをactionとして渡す形。
redux-thunk
アクションとして関数を渡すことができるようになる。
渡された関数を実行し、その結果を実際にdispatchする。
その関数の中に非同期処理を書く、つまり、action(関数)がdispatchされた後、reducerがactionを見る前に非同期処理を実行する形。
redux-api-middleware
RSAA(Redux Standard API-calling Actions)という独自のインターフェースを持つアクションをdispatchすると、それに従ってAPIコールし、その結果をFSAとしてdispatchする。
middlewareを使わずに独自の概念で実装する
独自で実装されている方も。
これ以外にも、自分でいい感じに実装できるなら試しても良いかも。
所感
雑な選定フロー
「Componentからはactionを投げるだけにしたい」
Noの場合はコンポーネント内で独自の実装作ったりして頑張る。つまり1か6。
「Componentからdispatchするactionはプレーンなオブジェクトであってほしい」
Yesの場合はsagaかredux-api-middlewareを使う。
Sagaのタスクの概念に共感できるならSagaが良い。
ただしだいぶ複雑になるので、アプリケーションの非同期処理が大して複雑にならないことが想定されるなら、sagaは重すぎるかも。
RSAA、個人的には発想が好き。あんまり使われてないのかな。
「Promiseそのまま渡したい」
Yesの場合はredux-promiseを使う。
Noの場合はredux-thunkを使う。
thunkの場合は非同期処理に限らずComponentに書きたくない処理を任せちゃうことができる。
今はsagaでごりごり書いてるけど、middlewareもっと使っても良いかも
- Reduxに新しい何かを導入せずに、Viewに書きたくない処理を任せられる場所
- ActionをDispatchしたときに共通の処理をしたい時に任せられる場所
というイメージ。
その他の注意点
sagaはコードスプリットがいい感じにできなかったり...