あなたはしません。
しかし... redux-sagaを使用する必要があります:)
ダン・アブラモフの答えは正しいですredux-thunk
が、かなり似ていますがより強力なredux-sagaについてもう少し話をします。
命令型VS宣言型
- DOM:jQueryは必須/ Reactは宣言型
- モナド:IOは必須/無料は宣言型
- Redux効果:
redux-thunk
必須/ redux-saga
宣言的
サンクを手に持っている場合、IOモナドやプロミスのように、いったん実行するとどうなるか簡単にわかりません。サンクをテストする唯一の方法は、サンクを実行して、ディスパッチャー(または、他のものとやり取りする場合は外界全体)をモックすることです。
モックを使用している場合は、関数型プログラミングを行っていません。
副作用のレンズを通して見たモックは、コードが不純であることを示すフラグであり、関数型プログラマの目には、何かが間違っていることの証拠です。ライブラリをダウンロードして、氷山が損傷していないことを確認できるようにする代わりに、航海する必要があります。ハードコアTDD / Javaの人から、Clojureでモックを行う方法を尋ねられました。答えは、通常はありません。通常、これはコードをリファクタリングする必要がある兆候と見なされます。
ソース
sagas(で実装されたものredux-saga
)は宣言型であり、FreeモナドやReactコンポーネントと同様に、モックなしでテストするのがはるかに簡単です。
この記事も参照してください:
最新のFPでは、プログラムを作成するべきではありません。プログラムの説明を作成する必要があります。プログラムの説明は、自由にイントロスペクト、変換、および解釈できます。
(実際、Redux-sagaはハイブリッドのようなものです。フローは必須ですが、効果は宣言的です)
混乱:アクション/イベント/コマンド...
フロントエンドの世界では、CQRS / EventSourcingやFlux / Reduxなどのバックエンドの概念がどのように関連しているのかについて、多くの混乱があります。主に、Fluxでは、命令コード(LOAD_USER
)とイベント(USER_LOADED
)。イベントソーシングと同様に、イベントをディスパッチするだけでよいと思います。
実際にサガを使用する
ユーザープロファイルへのリンクを持つアプリを想像してみてください。各ミドルウェアでこれを処理する慣用的な方法は次のようになります。
redux-thunk
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
このサガは次のように翻訳されます:
ユーザー名がクリックされるたびに、ユーザープロファイルを取得し、読み込まれたプロファイルでイベントをディスパッチします。
ご覧のとおり、にはいくつかの利点がありredux-saga
ます。
takeLatest
最後にクリックしたユーザー名のデータのみを取得することに関心があることを表現するための許可の使用法(ユーザーが多数のユーザー名を非常に速くクリックした場合の同時実行性の問題を処理します)。この種のものはサンクで難しいです。takeEvery
この動作を望まない場合は、使用することができます。
あなたはアクションクリエーターを純粋に保ちます。将来アクション検証(assertions / flow / typescript)を追加するのに役立つ可能性があるため、(saga put
とコンポーネントでdispatch
)actionCreatorsを保持することは依然として有用であることに注意してください。
効果が宣言的であるため、コードははるかにテスト可能になります
のようなrpcのような呼び出しをトリガーする必要はもうありませんactions.loadUser()
。UIは何が起こったかをディスパッチする必要があるだけです。私達は火のイベントはもう(常に過去形!)ではないアクション。これは、分離された「ダック」またはバインドされたコンテキストを作成できること、およびサガがこれらのモジュール式コンポーネント間のカップリングポイントとして機能できることを意味します。
つまり、発生したことと効果として何が発生するかの間の変換レイヤーを含める必要がなくなるため、ビューの管理がより簡単になります。
たとえば、無限のスクロールビューを想像してください。CONTAINER_SCROLLED
につながる可能性NEXT_PAGE_LOADED
がありますが、別のページをロードする必要があるかどうかを決定するのは、スクロール可能なコンテナの責任ですか?次に、最後のページが正常にロードされたかどうか、ロードしようとしているページがすでに存在するかどうか、またはロードするアイテムが残っていないかどうかなど、より複雑なことに注意する必要があります。私はそうは思いません:最大の再利用性のために、スクロール可能なコンテナはそれがスクロールされたことを単に記述すべきです。ページの読み込みは、そのスクロールの「ビジネス効果」です
一部の人は、ジェネレーターがローカル変数でreduxストアの外の状態を本質的に隠すことができると主張するかもしれませんが、タイマーなどを開始することによってサンク内の複雑なものを調整し始めると、とにかく同じ問題が発生します。そしてselect
、Reduxストアから状態を取得できるようにする効果があります。
Sagasはタイムトラベルが可能で、現在作業中の複雑なフローロギングと開発ツールも使用できます。以下は、すでに実装されているいくつかの単純な非同期フローロギングです。
デカップリング
Sagasはredux thunkを置き換えるだけではありません。それらはバックエンド/分散システム/イベントソーシングから来ています。
sagasがredux thunkをより優れたテスト容易性に置き換えるためにここにあるのは、よくある誤解です。実際、これはredux-sagaの実装の詳細にすぎません。宣言型効果の使用は、サンクよりもテスト容易性の点で優れていますが、sagaパターンは命令型または宣言型コードの上に実装できます。
そもそも、sagaは、長時間実行されるトランザクション(結果整合性)と、異なる境界コンテキスト(ドメイン駆動設計の専門用語)全体のトランザクションを調整できるソフトウェアです。
フロントエンドの世界でこれを単純化するために、widget1とwidget2があると想像してください。widget1のボタンがクリックされると、widget2に影響を与えるはずです。2つのウィジェットを結合する(つまり、widget1はwidget2をターゲットとするアクションをディスパッチする)代わりに、widget1はそのボタンがクリックされたことのみをディスパッチします。次に、サガはこのボタンのクリックをリッスンし、widget2が認識している新しいイベントをディスパッチしてwidget2を更新します。
これにより、単純なアプリには不要な間接参照のレベルが追加されますが、複雑なアプリケーションのスケーリングがより簡単になります。これで、widget1とwidget2を異なるnpmリポジトリに公開できるため、アクションのグローバルレジストリを共有しなくても、互いを知る必要がなくなります。2つのウィジェットは、個別に存続できる境界コンテキストになりました。それらは互いに整合している必要はなく、他のアプリでも再利用できます。サガは、ビジネスに意味のある方法でウィジェットを調整する2つのウィジェット間の結合点です。
分離の理由でRedux-sagaを使用できるReduxアプリの構成方法に関するすばらしい記事:
具体的なユースケース:通知システム
コンポーネントがアプリ内通知の表示をトリガーできるようにしたいのですが。ただし、独自のビジネスルール(最大3つの通知が同時に表示される、通知キューイング、4秒の表示時間など)を持つ通知システムにコンポーネントを高度に結合したくありません。
JSXコンポーネントで通知の表示/非表示をいつにするか決定したくありません。私はそれに通知を要求する能力を与え、複雑なルールをサガの中に残します。この種のものは、サンクやプロミスで実装するのが非常に困難です。
私はこれがどのようにサガで行われることができるかについてここで説明しました
なぜそれが佐賀と呼ばれるのですか?
サガという用語は、バックエンドの世界から来ています。私は長い議論の中で最初にその用語をYassine(Redux-sagaの作者)に紹介しました。
当初、この用語は紙で紹介されましたが、サガパターンは分散トランザクションの結果整合性を処理するために使用されるはずでしたが、その使用法はバックエンド開発者によってより広い定義に拡張され、「プロセスマネージャ」もカバーするようになりました。パターン(どういうわけか、元のsagaパターンはプロセスマネージャの特殊な形式です)。
今日、「佐賀」という用語は2つの異なることを説明できるため、混乱しています。これはredux-sagaで使用されるため、分散トランザクションを処理する方法ではなく、アプリ内のアクションを調整する方法について説明しています。redux-saga
と呼ぶこともできたredux-process-manager
。
以下も参照してください。
代替案
ジェネレーターを使用するアイデアが気に入らないが、サガパターンとそのデカップリングプロパティに興味がある場合は、名前を使用してまったく同じパターンを記述するredux-observableでも同じことを実現できますepic
が、RxJSを使用します。Rxに既に慣れている場合は、自宅にいるように感じるでしょう。
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
redux-sagaの役立つリソース
2017アドバイス
- Redux-sagaを使用するためだけに使いすぎないでください。テスト可能なAPI呼び出しだけでは価値がありません。
- ほとんどの単純なケースでは、プロジェクトからサンクを削除しないでください。
yield put(someActionThunk)
それが理にかなっている場合は、サンクを派遣することを躊躇しないでください。
Redux-saga(またはRedux-observable)を使用するのが怖いが、デカップリングパターンだけが必要な場合は、redux-dispatch-subscribeを確認してください。ディスパッチをリッスンし、リスナーで新しいディスパッチをトリガーできます。
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});