Reduxの非同期フローにミドルウェアが必要なのはなぜですか?

734
sbichenko 2016-01-03 11:09.

ドキュメントによると、「ミドルウェアがないと、Reduxストアは同期データフローのみをサポートします」。なぜそうなのかわかりません。コンテナコンポーネントが非同期APIを呼び出してからdispatch、アクションを呼び出せないのはなぜですか?

たとえば、フィールドとボタンという単純なUIを想像してみてください。ユーザーがボタンを押すと、フィールドにリモートサーバーからのデータが入力されます。

import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';

const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

class AsyncApi {
    static getFieldValue() {
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(Math.floor(Math.random() * 100));
            }, 1000);
        });
        return promise;
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <input value={this.props.field}/>
                <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                {this.props.isWaiting && <div>Waiting...</div>}
            </div>
        );
    }
}
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
    switch (action.type) {
        case ActionTypes.STARTED_UPDATING:
            return { ...state, isWaiting: true };
        case ActionTypes.UPDATED:
            return { ...state, isWaiting: false, field: action.payload };
        default:
            return state;
    }
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
    (state) => {
        return { ...state };
    },
    (dispatch) => {
        return {
            update: () => {
                dispatch({
                    type: ActionTypes.STARTED_UPDATING
                });
                AsyncApi.getFieldValue()
                    .then(result => dispatch({
                        type: ActionTypes.UPDATED,
                        payload: result
                    }));
            }
        };
    })(App);
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

エクスポートされたコンポーネントがレンダリングされると、ボタンをクリックでき、入力が正しく更新されます。

呼び出しのupdate関数に注意してくださいconnect。アプリに更新中であることを通知するアクションをディスパッチしてから、非同期呼び出しを実行します。呼び出しが終了すると、指定された値が別のアクションのペイロードとしてディスパッチされます。

このアプローチの何が問題になっていますか?ドキュメントが示唆しているように、なぜReduxThunkまたはReduxPromiseを使用したいのですか?

編集: Reduxリポジトリで手がかりを検索したところ、過去にはアクションクリエーターが純粋関数である必要があることがわかりました。たとえば、非同期データフローについてより適切な説明を提供しようとしているユーザーは次のとおりです。

アクションクリエーター自体はまだ純粋関数ですが、それが返すサンク関数はそうである必要はなく、非同期呼び出しを行うことができます

アクションクリエーターはもはや純粋である必要はありません。それで、サンク/プロミスミドルウェアは過去に間違いなく必要でしたが、これはもはやそうではないようですか?

11 answers

757
Dan Abramov 2016-01-05 10:50.

このアプローチの何が問題になっていますか?ドキュメントが示唆しているように、なぜReduxThunkまたはReduxPromiseを使用したいのですか?

このアプローチには何の問題もありません。異なるコンポーネントが同じアクションを実行するため、一部のアクションをデバウンスしたり、IDの自動インクリメントなどのローカル状態をアクション作成者の近くに維持したりするため、大規模なアプリケーションでは不便です。アクションクリエーターを個別の機能に抽出するためのメンテナンスの観点。

より詳細なウォークスルーについては、タイムアウト付きのReduxアクションをディスパッチする方法は?読むことができます。

ReduxThunkやReduxPromiseのようなミドルウェアは、サンクやプロミスをディスパッチするための「シンタックスシュガー」を提供するだけですが、それ使用する必要はありません。

したがって、ミドルウェアがないと、アクション作成者は次のようになります。

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

しかし、サンクミドルウェアを使用すると、次のように記述できます。

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

したがって、大きな違いはありません。後者のアプローチについて私が気に入っていることの1つは、コンポーネントがアクション作成者が非同期であることを気にしないことです。dispatch通常どおりに呼び出すだけmapDispatchToPropsで、短い構文などでそのようなアクションクリエーターをバインドするために使用することもできます。コンポーネントはアクションクリエーターの実装方法を認識せず、さまざまな非同期アプローチ(Redux Thunk、Redux Promise、Redux Saga)を切り替えることができます。 )コンポーネントを変更せずに。一方、前者の明示的なアプローチでは、コンポーネントは特定の呼び出しが非同期であることを正確dispatchに認識しており、何らかの規則によって(たとえば、同期パラメーターとして)渡す必要があります。

また、このコードがどのように変わるかについても考えてください。2番目のデータ読み込み関数が必要であり、それらを1つのアクションクリエーターに結合するとします。

最初のアプローチでは、どのようなアクションクリエーターを呼んでいるかに注意する必要があります。

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

Redux Thunkを使用すると、アクションクリエーターdispatchは他のアクションクリエーターの結果を得ることができ、それらが同期か非同期かを考えることさえできません。

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

このアプローチでは、後でアクションクリエーターに現在のReduxの状態を調べてもらいたい場合はgetState、呼び出し元のコードをまったく変更せずに、サンクに渡された2番目の引数を使用できます。

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

同期するように変更する必要がある場合は、呼び出しコードを変更せずにこれを行うこともできます。

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

したがって、ReduxThunkやReduxPromiseなどのミドルウェアを使用する利点は、コンポーネントがアクションクリエーターの実装方法、Redux状態を気にするかどうか、同期か非同期か、他のアクションクリエーターを呼び出すかどうかを認識しないことです。 。欠点は少し間接的ですが、実際のアプリケーションではそれだけの価値があると考えています。

最後に、Redux Thunkとその仲間は、Reduxアプリでの非同期リクエストへの可能なアプローチの1つにすぎません。もう1つの興味深いアプローチは、Redux Sagaです。これを使用すると、実行中のデーモン(「sagas」)を定義して、アクションが発生したときにアクションを実行し、アクションを出力する前に要求を変換または実行できます。これにより、ロジックがアクションクリエーターからサガに移動します。あなたはそれをチェックして、後であなたに最も合うものを選ぶことを望むかもしれません。

Reduxリポジトリで手がかりを検索したところ、過去にはアクションクリエーターは純粋関数である必要があることがわかりました。

これは正しくありません。ドキュメントはこれを言ったが、ドキュメントは間違っていた。
アクションクリエーターは純粋関数である必要はありませんでした。
それを反映するようにドキュメントを修正しました。

468
Sebastien Lorber 2016-01-06 14:43.

あなたはしません。

しかし...あなたは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この動作を望まない場合は、を使用できます。

アクションクリエーターを純粋に保ちます。将来的にアクション検証(アサーション/フロー/タイプスクリプト)を追加するのに役立つ可能性があるため、actionCreators(sagasputおよびコンポーネント内dispatch)を保持することは依然として有用であることに注意してください。

効果が宣言型であるため、コードははるかにテスト可能になります

のようなrpcのような呼び出しをトリガーする必要はもうありませんactions.loadUser()。UIは、発生したことをディスパッチする必要があります。イベントを発生させるだけで(常に過去形です!)、アクションは発生しません。これは、分離された「アヒル」または境界コンテキストを作成でき、サガがこれらのモジュラーコンポーネント間の結合ポイントとして機能できることを意味します。

つまり、ビューは、発生したことと効果として発生するはずの間に翻訳レイヤーを含める必要がなくなるため、管理がより簡単になります。

たとえば、無限のスクロールビューを想像してみてください。CONTAINER_SCROLLEDにつながる可能性NEXT_PAGE_LOADEDがありますが、別のページをロードする必要があるかどうかを決定するのは、本当にスクロール可能なコンテナの責任ですか?次に、最後のページが正常に読み込まれたかどうか、読み込もうとしているページがすでにあるかどうか、読み込むアイテムが残っていないかどうかなど、より複雑なことに注意する必要があります。私はそうは思いません。最大限の再利用性を得るには、スクロール可能なコンテナは、スクロールされたことを説明するだけです。ページの読み込みは、そのスクロールの「ビジネス効果」です

ジェネレーターは本質的にローカル変数を使用してreduxストアの外部に状態を隠すことができると主張する人もいるかもしれませんが、タイマーなどを開始してサンク内の複雑なものをオーケストレーションし始めると、とにかく同じ問題が発生します。そしてselect、Reduxストアからいくつかの状態を取得できるようになる効果があります。

Sagasはタイムトラベルでき、現在作業中の複雑なフローロギングと開発ツールも可能にします。すでに実装されているいくつかの単純な非同期フローロギングを次に示します。

デカップリング

Sagasはreduxサンクに取って代わるだけではありません。それらはバックエンド/分散システム/イベントソーシングから来ています。

非常に一般的な誤解は、サガがあなたのreduxサンクをより良いテスト容易性に置き換えるためにここにあるということです。実際、これはredux-sagaの実装の詳細にすぎません。宣言型効果を使用することは、テスト容易性のためにサンクよりも優れていますが、sagaパターンは命令型または宣言型コードの上に実装できます。

そもそも、sagaは、長時間実行されるトランザクション(結果整合性)と、さまざまな境界コンテキストにわたるトランザクション(ドメイン駆動設計の専門用語)を調整できるソフトウェアです。

フロントエンドの世界でこれを単純化するために、widget1とwidget2があると想像してください。widget1のいくつかのボタンがクリックされると、widget2に影響を与えるはずです。2つのウィジェットを結合する(つまり、widget1はwidget2をターゲットとするアクションをディスパッチする)代わりに、widget1はボタンがクリックされたことのみをディスパッチします。次に、佐賀はこのボタンのクリックをリッスンし、widget2が認識している新しいイベントをディスパッチしてwidget2を更新します。

これにより、単純なアプリには不要なレベルの間接参照が追加されますが、複雑なアプリケーションのスケーリングがより簡単になります。ウィジェット1とウィジェット2を異なるnpmリポジトリに公開できるようになったため、アクションのグローバルレジストリを共有しなくても、お互いを知る必要がなくなりました。2つのウィジェットは、別々に存在できる境界付きコンテキストになりました。それらは互いに一貫している必要はなく、他のアプリでも再利用できます。佐賀は、ビジネスにとって意味のある方法でそれらを調整する2つのウィジェット間の結合点です。

Reduxアプリを構築する方法に関するいくつかの素晴らしい記事。デカップリングの理由でRedux-sagaを使用できます。

具体的なユースケース:通知システム

コンポーネントがアプリ内通知の表示をトリガーできるようにしたい。ただし、コンポーネントを、独自のビジネスルール(同時に表示される最大3つの通知、通知キュー、4秒の表示時間など)を持つ通知システムに高度に結合させたくありません。

通知を表示/非表示にするタイミングをJSXコンポーネントに決定させたくありません。通知をリクエストできるようにし、複雑なルールをサガ内に残します。この種のものは、サンクやプロミスで実装するのは非常に困難です。

ここでは、これをsagaで行う方法について説明しました。

なぜ佐賀と呼ばれるのですか?

佐賀という用語は、バックエンドの世界から来ています。私は最初、長い議論の中でYassine(Redux-sagaの作者)にその用語を紹介しました。

当初、その用語はで紹介されましたが、sagaパターンは分散トランザクションの結果整合性を処理するために使用されるはずでしたが、その使用法はバックエンド開発者によってより広い定義に拡張され、現在は「プロセスマネージャー」もカバーしています。パターン(どういうわけか、元の佐賀パターンはプロセスマネージャーの特殊な形式です)。

今日、「佐賀」という用語は、2つの異なることを表すことができるため、混乱を招きます。redux-sagaで使用されているため、分散トランザクションを処理する方法ではなく、アプリのアクションを調整する方法を説明しています。redux-sagaと呼ばれることもありますredux-process-manager

参照:

代替案

ジェネレーターを使用するというアイデアが気に入らないが、sagaパターンとそのデカップリングプロパティに興味がある場合は、名前を使用してまったく同じパターンを記述するredux-observableを使用して、epicRxJSを使用して同じことを実現することもできます。すでに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' });
  }
});
34
acjay 2016-01-04 17:28.

簡単な答え:私には非同期の問題に対する完全に合理的なアプローチのようです。いくつかの注意点があります。

仕事を始めたばかりの新しいプロジェクトに取り組むとき、私は非常によく似た考えを持っていました。私は、Reactコンポーネントツリーの根性から離れた方法でストアを更新し、コンポーネントを再レンダリングするためのバニラReduxのエレガントなシステムの大ファンでした。dispatch非同期を処理するためにそのエレガントなメカニズムにフックするのは私には奇妙に思えました。

私は、プロジェクトから除外したライブラリーにあるものと非常によく似たアプローチを採用することになりました。これをreact-redux-controllerと呼びます。

いくつかの理由で、上記の正確なアプローチを採用できませんでした。

  1. あなたがそれを書いたように、それらのディスパッチ関数はストアにアクセスできません。UIコンポーネントにディスパッチ関数が必要とするすべての情報を渡させることで、これをある程度回避できます。しかし、これにより、これらのUIコンポーネントがディスパッチングロジックに不必要に結合されると私は主張します。さらに問題なのは、ディスパッチ機能が非同期継続で更新された状態にアクセスする明確な方法がないことです。
  2. ディスパッチ関数はdispatch、字句スコープを介してそれ自体にアクセスできます。これにより、connectステートメントが手に負えなくなった後のリファクタリングのオプションが制限されますupdate。その1つの方法だけでは、かなり扱いにくいように見えます。したがって、これらのディスパッチャ関数を個別のモジュールに分割する場合に、それらを構成できるようにするためのシステムが必要です。

まとめるdispatchと、イベントのパラメーターとともに、ストアをディスパッチング関数に注入できるように、いくつかのシステムを調整する必要があります。この依存性注入に対する3つの合理的なアプローチを知っています。

  • redux-thunkは、これらをサンクに渡すことにより、機能的な方法でこれを行います(ドームの定義により、サンクとはまったく異なります)。私は他のdispatchミドルウェアアプローチを使用していませんが、基本的には同じだと思います。
  • react-redux-controllerはコルーチンでこれを行います。ボーナスとしてconnect、生の正規化されたストアを直接操作するのではなく、最初の引数として渡した可能性のある関数である「セレクター」へのアクセスも提供します。
  • またthis、考えられるさまざまなメカニズムを通じて、コンテキストにそれらを注入することにより、オブジェクト指向の方法でそれを行うこともできます。

更新

この難問の一部はreact-reduxの制限である私は思います。connect状態スナップショットを取得するための最初の引数。ただし、ディスパッチは取得しません。2番目の引数はディスパッチを取得しますが、状態は取得しません。どちらの引数も、継続/コールバック時に更新された状態を確認できるため、現在の状態を閉じるサンクを取得しません。

26
XML 2017-03-06 16:46.

Abramovの目標(そして理想的には誰もが)は、複雑さ(および非同期呼び出し)を最も適切な場所カプセル化することです。

標準のReduxデータフローでこれを行うのに最適な場所はどこですか?どうですか:

  • レデューサー?ありえない。それらは副作用のない純粋関数でなければなりません。ストアの更新は、深刻で複雑なビジネスです。それを汚染しないでください。
  • ダムビューコンポーネント?確かにいいえ。彼らには1つの懸念があります。それは、プレゼンテーションとユーザーインタラクションであり、可能な限りシンプルにする必要があります。
  • コンテナコンポーネント?可能ですが、最適ではありません。コンテナは、ビューに関連する複雑さをカプセル化し、ストアと対話する場所であるという点で理にかなっていますが、次のようになります。
    • コンテナはダムコンポーネントよりも複雑である必要がありますが、それでも単一の責任があります。それは、ビューと状態/ストアの間のバインディングを提供することです。非同期ロジックは、それとはまったく別の関心事です。
    • コンテナに配置することで、非同期ロジックを単一のコンテキスト、単一のビュー/ルートにロックすることになります。悪いアイデア。理想的には、すべて再利用可能で、完全に分離されています。
  • S青梅他のサービスモジュールは?悪い考え:ストアへのアクセスを注入する必要があります。これは、保守性/テスト性の悪夢です。Reduxの粒度に合わせて、提供されているAPI /モデルのみを使用してストアにアクセスすることをお勧めします。
  • アクションとそれを解釈するミドルウェア?何故なの?!手始めに、それは私たちが残した唯一の主要なオプションです。:-)より論理的には、アクションシステムは、どこからでも使用できる分離された実行ロジックです。ストアにアクセスでき、より多くのアクションをディスパッチできます。アプリケーション周辺の制御とデータのフローを整理するという単一の責任があり、ほとんどの非同期はそれにぴったりと適合します。
    • アクションクリエーターはどうですか?アクション自体ではなく、ミドルウェアで非同期を実行しないのはなぜですか?
      • まず、最も重要なのは、ミドルウェアのように、作成者がストアにアクセスできないことです。つまり、新しい条件付きアクションをディスパッチしたり、ストアから読み取って非同期を作成したりすることはできません。
      • したがって、複雑さを必要性の複雑な場所に保ち、他のすべてを単純に保ちます。そうすれば、作成者は、テストが容易な、単純で比較的純粋な関数にすることができます。
18
Michelle Tilley 2016-01-04 20:20.

最初に尋ねられる質問に答えるには:

コンテナコンポーネントが非同期APIを呼び出してから、アクションをディスパッチできないのはなぜですか?

これらのドキュメントはReduxとReactではなく、Redux用であることに注意してください。Reactコンポーネントに接続されたReduxストアは、あなたが言うことを正確に実行できますが、ミドルウェアのないプレーンジェーンReduxストアは、dispatchプレーンなol 'オブジェクト以外の引数を受け入れません。

もちろん、ミドルウェアがなくても実行できます

const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));

しかし、それは非同期に包まれている同様のケースの周りReduxのではなく、扱いによってReduxの。したがって、ミドルウェアは、に直接渡すことができるものを変更することによって非同期を可能にしますdispatch


そうは言っても、あなたの提案の精神は有効だと思います。Redux + Reactアプリケーションで非同期を処理できる方法は他にも確かにあります。

ミドルウェアを使用する利点の1つは、アクションクリエーターがどのように接続されているかを正確に気にすることなく、通常どおりアクションクリエーターを引き続き使用できることです。たとえば、を使用するredux-thunkと、作成したコードは次のようになります。

function updateThing() {
  return dispatch => {
    dispatch({
      type: ActionTypes.STARTED_UPDATING
    });
    AsyncApi.getFieldValue()
      .then(result => dispatch({
        type: ActionTypes.UPDATED,
        payload: result
      }));
  }
}

const ConnectedApp = connect(
  (state) => { ...state },
  { update: updateThing }
)(App);

これは元のファイルとそれほど変わらないように見えます—少しシャッフルされているだけです—そしてconnectそれupdateThingが非同期である(または非同期である必要がある)ことを知りません。

あなたもサポートしたい場合は、約束観測サガ、または狂気のカスタム高度宣言型アクションクリエイター、そしてReduxのはちょうどあなたがに渡すものを変更することでそれを行うことができますdispatch(別名、あなたはアクションクリエイターから何を返します)。Reactコンポーネント(またはconnect呼び出し)をいじくり回す必要はありません。

11
Alireza 2017-11-19 02:22.

OK、のは非常に質問に答えることを、ミドルウェアが最初の作業方法を確認するために始めましょう、これはソースコードであるpplyMiddleWareのReduxの関数:

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

この部分を見て、ディスパッチがどのように関数になるかを確認してください。

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}
  • 各ミドルウェアには、名前付き引数としてdispatchgetState関数が与えられることに注意してください。

OK、これは、Reduxで最も使用されているミドルウェアの1つであるRedux-thunkが自己紹介する方法です。

Redux Thunkミドルウェアを使用すると、アクションの代わりに関数を返すアクションクリエーターを作成できます。サンクは、アクションのディスパッチを遅らせるため、または特定の条件が満たされた場合にのみディスパッチするために使用できます。内部関数は、ストアメソッドdispatchおよびgetStateをパラメーターとして受け取ります。

ご覧のとおり、アクションではなく関数が返されます。つまり、関数であるため、いつでも待機して呼び出すことができます...

それで、一体何がサンクですか?それがウィキペディアで紹介されている方法です:

コンピュータプログラミングでは、サンクは別のサブルーチンに追加の計算を挿入するために使用されるサブルーチンです。サンクは主に、必要になるまで計算を遅らせるため、または他のサブルーチンの最初または最後に操作を挿入するために使用されます。コンパイラコード生成やモジュラープログラミングには、他にもさまざまなアプリケーションがあります。

この用語は、「考える」の冗談の派生語として始まりました。

サンクは、式をラップして評価を遅らせる関数です。

//calculation of 1 + 2 is immediate 
//x === 3 
let x = 1 + 2;

//calculation of 1 + 2 is delayed 
//foo can be called later to perform the calculation 
//foo is a thunk! 
let foo = () => 1 + 2;

コンセプトがいかに簡単で、非同期アクションの管理にどのように役立つかをご覧ください...

それがなくても生きていけるものですが、プログラミングでは、物事を行うためのより良い、きちんとした、適切な方法が常にあることを忘れないでください...

5
SM Chinna 2018-02-10 02:03.

Redux-sagaを使用することは、React-redux実装で最高のミドルウェアです。

例:store.js

  import createSagaMiddleware from 'redux-saga';
  import { createStore, applyMiddleware } from 'redux';
  import allReducer from '../reducer/allReducer';
  import rootSaga from '../saga';

  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(
     allReducer,
     applyMiddleware(sagaMiddleware)
   )

   sagaMiddleware.run(rootSaga);

 export default store;

そしてsaga.js

import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';

export function* updateLesson(){
   try{
       yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
       yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js 
   }
   catch(e){
      console.log("error",e)
     }
  }

export function* updateDetail(action) {
  try{
       //To write store update details
   }  
    catch(e){
       console.log("error",e)
    } 
 }

export default function* rootSaga(){
    yield [
        updateLesson()
       ]
    }

そしてaction.js

 export default function updateFruit(props,fruit) {
    return (
       {
         type:"UPDATE_DETAIL",
         payload:fruit,
         props:props
       }
     )
  }

そしてreducer.js

import {combineReducers} from 'redux';

const fetchInitialData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
 const updateDetailsData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
const allReducers =combineReducers({
   data:fetchInitialData,
   updateDetailsData
 })
export default allReducers; 

そしてmain.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';

const initialState = {};
const store = configureStore(initialState, browserHistory);

ReactDOM.render(
       <Provider store={store}>
          <App />  /*is your Component*/
       </Provider>, 
document.getElementById('app'));

これを試してください..動作しています

4
Daniel 2019-02-21 14:47.

同期アクションクリエーターがあり、次に非同期アクションクリエーターがあります。

同期アクションクリエーターとは、呼び出すとすぐに、関連するすべてのデータがオブジェクトに添付されたActionオブジェクトを返し、レデューサーで処理できるようにするものです。

非同期アクションクリエーターは、最終的にアクションをディスパッチする準備ができるまでに少し時間がかかるものです。

定義上、ネットワークリクエストを行うアクションクリエーターがいる場合は常に、非同期アクションクリエーターとしての資格があります。

Reduxアプリケーション内に非同期アクションクリエーターを配置したい場合は、それらの非同期アクションクリエーターを処理できるようにするミドルウェアと呼ばれるものをインストールする必要があります。

これは、非同期アクションにカスタムミドルウェアを使用することを通知するエラーメッセージで確認できます。

では、ミドルウェアとは何ですか?Reduxの非同期フローにミドルウェアが必要なのはなぜですか?

redux-thunkなどのreduxミドルウェアのコンテキストでは、ミドルウェアは非同期アクションクリエーターを処理するのに役立ちます。これは、Reduxがそのままでは処理できないものだからです。

Reduxサイクルに統合されたミドルウェアを使用して、まだアクションクリエーターを呼び出しています。これにより、ディスパッチされるアクションが返されますが、アクションをディスパッチすると、すべてのレデューサーに直接送信するのではなく、アクションは、アプリケーション内のすべての異なるミドルウェアを介して送信されると言います。

単一のReduxアプリ内に、必要な数のミドルウェアを含めることができます。ほとんどの場合、私たちが取り組んでいるプロジェクトでは、1つまたは2つのミドルウェアをReduxストアに接続します。

ミドルウェアは、ディスパッチするすべてのアクションで呼び出されるプレーンなJavaScript関数です。その機能の内部で、ミドルウェアは、アクションがレデューサーのいずれかにディスパッチされるのを停止する機会があります。アクションを変更したり、アクションをいじったりすることができます。たとえば、コンソールログを記録するミドルウェアを作成できます。視聴を楽しむためだけにディスパッチするすべてのアクション。

プロジェクトに依存関係としてインストールできるオープンソースミドルウェアは膨大な数にのぼります。

オープンソースミドルウェアを利用したり、依存関係としてインストールしたりするだけではありません。独自のカスタムミドルウェアを作成して、Reduxストア内で使用できます。

ミドルウェアの最も一般的な使用法の1つ(そしてあなたの答えを得るため)は、非同期アクションクリエーターを扱うことです。おそらく、そこにある最も人気のあるミドルウェアはredux-thunkであり、非同期アクションクリエーターを扱うのを助けることです。

非同期アクションクリエーターの処理にも役立つミドルウェアには、他にも多くの種類があります。

4
Mselmi Ali 2019-08-14 02:24.

質問に答えるには:

コンテナコンポーネントが非同期APIを呼び出してから、アクションをディスパッチできないのはなぜですか?

私は少なくとも2つの理由で言うでしょう:

第一の理由は、それがの仕事ではないですが、関心事の分離でaction creatorコールするapiと、データを取り戻す、あなたへの2つの引数を渡すために持っている必要がありaction creator functionaction typeそしてpayload

2番目の理由は、redux storeが必須のアクションタイプとオプションでaを持つプレーンオブジェクトを待機しているためですpayload(ただし、ここではペイロードも渡す必要があります)。

アクションの作成者は、次のようなプレーンオブジェクトである必要があります。

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

そして、あなたの結果への適切なRedux-Thunk midlewareへの仕事。dispacheapi callaction

3
Feras 2020-05-16 20:54.

エンタープライズプロジェクトで作業する場合、単純な非同期フローでは利用できない(saga)など、ミドルウェアで利用できる多くの要件があります。以下にいくつかを示します。

  • リクエストを並行して実行する
  • 待つ必要なしに将来の行動を引っ張る
  • ノンブロッキングコールレース効果、最初のピックアップ例
  • プロセスを開始するための応答タスクの順序付け(最初の呼び出しで最初)
  • 作曲
  • タスクのキャンセルタスクを動的にフォークします。
  • reduxミドルウェアの外部でSagaを実行する同時実行をサポートします。
  • チャネルの使用

リストは長いですが、sagaドキュメントの高度なセクションを確認してください

0
coder9833idls 2020-09-09 00:31.

Reduxはアクションの代わりに関数を返すことはできません。それはただの事実です。それが人々がサンクを使う理由です。これらの14行のコードを読んで、非同期サイクルがいくつかの追加された関数レイヤーでどのように機能するかを確認してください。

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

https://github.com/reduxjs/redux-thunk

Related questions

MORE COOL STUFF

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

今週のコミックコンですべての素晴らしいものに追いつく方法

今週のコミックコンですべての素晴らしいものに追いつく方法

サンディエゴコミックコンは今週開幕し、オタクのアナウンス、ポスター、予告編、お気に入りの番組や映画のからかいでいっぱいになります。SDCCは、コンベンションフロア全体の多くのパネルで行われているため、すべてに対応するのは難しい場合があります。

Googleの9千万ドルの和解はアプリ開発者にとってもGoogleにとっても勝利ですか?

Googleの9千万ドルの和解はアプリ開発者にとってもGoogleにとっても勝利ですか?

小さなアプリ開発者は金曜日に発表された法的な和解でグーグルから9千万ドルをこじ開けた。アップルとの同様の合意に続いて熱くなった。金曜日のブログ投稿で、Googleは、Androidメーカーが市場での優位性を悪用してPlayストア経由でのアプリ内購入に対して30%の料金を不当に請求したと主張するアプリ開発者との訴訟を解決するために、9千万ドルを支払うことに合意したと述べました。

RadioShackのTwitterはハッキングされていませんでした、それはただの暗号のサクラです

RadioShackのTwitterはハッキングされていませんでした、それはただの暗号のサクラです

今週、RadioShackのTwitterアカウントは、奇妙なものから完全にひどいものになりました。短い順序で、会社のフィード全体が、バイブレーター、「ビッグティット」(スペルミス)、有名人やその他の企業アカウントを荒らしているツイートなど、NSFW素材の真の山になりました。

ヒッグス粒子から10年後、物理学にとって次の大きなものは何ですか?

ヒッグス粒子から10年後、物理学にとって次の大きなものは何ですか?

大型ハドロン衝突型加速器のトンネル内にあるコンパクトミュオンソレノイド(CMS)検出器。2012年7月4日、CERNの科学者たちは、1960年代に最初に提案された素粒子であるヒッグス粒子の観測を確認しました。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

Un breve viaje espacial sobre conceptualizar el diseño

Complicarse la vida, mezclar churros con meninas (nada de ovejas) y encontrar valor en un trastero que adquiriste en una puja.

Un breve viaje espacial sobre conceptualizar el diseño

Bien. Hay un momento en toda salida al espacio exterior en el que de la tensión, la velocidad y las altas temperaturas derivadas del cruce de estratosfera a ionosfera se pasa a un momento de súbita calma, donde se despliega la vista completa del paisaje espacial que nos rodea.

Seguindo Todos os Protocolos (2022), de Fábio Leal

Seguindo Todos os Protocolos (2022), de Fábio Leal

Chico quer transar. Até aí, tudo bem.

多元宇宙—Junø

多元宇宙—Junø

チェーン間アカウントがJunoに登場します。異なるブロックチェーン間でスマートコントラクトの構成可能性と真の相互運用性を提供します。

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

1.ドラマを見た後、起業する考えはありますか?あなたのビジネスはボトルネックに遭遇しましたか?方向性がなくてわからない場合は、ドラマを追いかけて行くことを心からお勧めします。(?)ブラフではなく、最も完璧なビジネス例を隠すドラマがあります。2.ブレイキング・バッドとその弁護士ドラマ「ブレイキング・バッド」を見た友人たちは、演劇の中で、穏やかな表情で、弁護士のソウル・グッドマンに深く感銘を受けなければなりません。口を開けて、感覚の弱い傭兵の性格を持っています。道徳の面で、サル・グッドマンは無意識のうちに劇に欠かせない役割を果たし、彼自身のシリーズ「絶望的な弁護士」(ベター・コール・ソール)を生み出しました。ウェントウのテキストとビデオは、劇中のソウル・グッドマンのテレビコマーシャルです。製品(サービス)、競争戦略、市場ポジショニング、ブランド名、ターゲット顧客グループ、コミュニケーション軸から広告まで、サル・グッドマンの役割のビジネス設定は、「最低」と見なすことができる超超超超超超完全です。ブランドコミュニケーションのコスト」「変化」のモデル。なぜ?私の分析をご覧ください。3.ソウル・グッドマンの「事業戦略」1.基本情報ブランド名:Saul Goodman製品:法律相談サービス対象顧客:麻薬中毒、飲酒運転、事故など。法律知識の欠如は、一般的に公立弁護士にしか余裕がなく、真面目な弁護士も「特別な法律を持つ消費者」を避けます。恐れてはいけない「​​ニーズ」。コミュニケーションの主軸:この国のすべての男性、女性、子供は有罪判決を受けるまで無実だと思います。地域:アルバカーキ市スローガン:Thrallに電話したほうがいいです!(ベター・コール・ソール)広告:2つの可能性のある犯罪状況をシミュレートします+サウルの主張+サウルのスローガン2をより適切に呼び出します。

Language