web-dev-qa-db-ja.com

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

"ミドルウェアがなければ、Reduxストアは同期データフローのみをサポートします" /。なぜそうなるのか私は理解できません。コンテナコンポーネントが非同期APIを呼び出してからアクションをdispatchできないのはなぜですか?

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

A field and a button

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関数に注意してください。アプリに更新中であることを通知するアクションをディスパッチしてから、非同期呼び出しを実行します。呼び出しが終了すると、提供された値は別のアクションのペイロードとして送出されます。

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

編集: 私は手がかりを求めてReduxリポジトリを検索しましたが、Action Creatorsは過去には純粋な関数である必要があることがわかりました。例えば、 非同期データフローについてより良い説明を提供しようとしているユーザーです。

アクション作成者自身はまだ純粋な関数ですが、それが返すさんく関数は必ずしも必要ではありません、そしてそれは私たちの非同期呼び出しをすることができます

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

512
sbichenko

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

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

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

Redux ThunkやRedux Promiseなどのミドルウェアは、サンクやプロミスをディスパッチするための「構文シュガー」を提供しますが、使用する必要はありません

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

// 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
}

ただし、Thunkミドルウェアを使用すると、次のように記述できます。

// 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サンク、Redux Promise、Redux Saga)。一方、前者の明示的なアプローチでは、コンポーネントは特定の呼び出しが非同期であることをexactly知っており、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サンクアクションクリエーターは、他のアクションクリエーターの結果を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状態を調べる場合、呼び出しコードをまったく変更せずに、サンクに渡される2番目のgetState引数を使用できます。

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')
  }
}

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

最後に、Redux Thunkとその仲間は、Reduxアプリでの非同期リクエストへの可能なアプローチの1つにすぎません。別の興味深いアプローチは Redux Saga です。これにより、アクションを出力する前にアクションを実行し、リクエストを変換または実行する長時間実行デーモン(「サガ」)を定義できます。これにより、アクション作成者からロジックがsagasに移動します。あなたはそれをチェックアウトし、後であなたに最も合ったものを選びたいかもしれません。

手がかりを求めてReduxリポジトリを検索しましたが、過去にはAction Creatorが純粋な機能である必要がありました。

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

580
Dan Abramov

あなたは違います。

しかし、...あなたはredux-sagaを使うべきです:)

Dan Abramovの答えはredux-thunkについて正しいですが、私は redux-saga についてもう少し話しますが、これは非常に似ていますがより強力です。

命令型VS宣言型

  • _ dom _ :jQueryは必須です/ Reactは宣言的です
  • モナド :IOは必須です/ Freeは宣言的です
  • Redux効果 redux-thunkは必須です/ redux-sagaは宣言的です

IOモナドや約束のようにあなたが自分の手にサンクを持っているとき、あなたは実行したらそれが何をするのか簡単にはわかりません。サンクをテストする唯一の方法は、サンクを実行し、ディスパッチャ(またはそれが他のものと相互作用する場合は外部全体)をモックすることです。

あなたがモックを使っているなら、あなたは関数型プログラミングをしていません。

副作用のレンズを通して見て、モックはあなたのコードが不純であるというフラグであり、そして機能的なプログラマーの目では、何かが間違っているという証拠。氷山が損なわれていないことを確認するために図書館をダウンロードする代わりに、私たちはそれの周りを航海しているべきです。筋金入りのTDD/Javaの男がかつて私があなたがClojureでどうやってあざけるのかを私に尋ねた。答えは、私たちは通常しないでください。私たちは通常、コードをリファクタリングする必要があるというサインとしてそれを見ています。

ソース

サガは(redux-sagaに実装されているように)宣言的で、Free monadやReactコンポーネントのように、モックなしでテストするのがはるかに簡単です。

こちらも参照してください article

現代の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)を追加するのに役立つかもしれないので、actionCreatorsを(sagas putとコンポーネントdispatchに)保持することは依然として有用です。

効果が宣言的であるため、あなたのコードはずっとテストしやすくなります

actions.loadUser()のようなrpc風の呼び出しを引き起こす必要はもうありません。あなたのUIはちょうど起こったものをディスパッチする必要があります。我々は events (常に過去形で発火する)だけを発動し、もはや行動を起こさない。つまり、分離された "アヒル" または Bounded Contexts を作成でき、sagaがこれらのモジュールコンポーネント間のカップリングポイントとして機能できるということです。

これは、起こったことと結果として起こるべきこととの間にその翻訳レイヤを含める必要がなくなるため、ビューの管理がより簡単になることを意味します。

たとえば、無限スクロールビューを想像してください。 CONTAINER_SCROLLEDNEXT_PAGE_LOADEDにつながる可能性がありますが、別のページをロードするかどうかを決定するのは、スクロール可能なコンテナの責任です。それから最後のページが正常にロードされたかどうか、すでにロードしようとしているページがあるのか​​、それともロードする項目がもうないのかなど、もっと複雑なことを知っておく必要があります。私はそうは思わない:最大限に再利用できるようにするためにはスクロール可能なコンテナは単にそれがスクロールされたことを記述すべきである。ページの読み込みはそのスクロールの「ビジネス効果」です。

ジェネレータはローカル変数でreduxストアの外に本質的に状態を隠すことができると主張するかもしれませんが、タイマなどを使ってサンクの中の複雑なものを調整しようとすれば、とにかく同じ問題があります。そしてselect効果があります。これはあなたのReduxストアから何らかの状態を取得することを許可します。

Sagasは時間をかけて移動することができ、また現在取り組んでいる複雑なフローロギングや開発ツールも可能にします。これはすでに実装されているいくつかの簡単な非同期フローロギングです:

saga flow logging

デカップリング

Sagasはreduxさんくを置き換えるだけではありません。それらはバックエンド/分散システム/イベントソーシングから来ます。

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

そもそも、sagaは、長期にわたるトランザクション(最終的な一貫性)、およびさまざまな境界のあるコンテキスト間でのトランザクション(ドメイン駆動設計の専門用語)を調整することを可能にするソフトウェアの一部です。

フロントエンドの世界でこれを単純化するために、widget1とwidget2があるとします。 widget1のいずれかのボタンをクリックすると、widget2にも影響があります。 2つのウィジェットを結合する代わりに(つまり、widget1はwidget2をターゲットとするアクションをディスパッチする)、そのボタンがクリックされたことをディスパッチするだけです。それからサーガはこのボタンクリックを聞き、そしてwidget2が知っている新しいイベントをディスパッチすることによってwidget2を更新します。

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

あなたのReduxアプリをどのように構築するかについてのいくつかのいい記事、あなたはそれを分離の理由でRedux-sagaを使うことができます:

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

自分のコンポーネントがアプリ内通知の表示をトリガーできるようにしたいです。しかし、私は自分のコンポーネントがそれ自身のビジネスルール(同時に表示される最大3つの通知、通知キューイング、4秒の表示時間など)を持つ通知システムと密接に結び付けられることを望まない。

通知をいつ表示/非表示にするかをJSXコンポーネントに決定させたくありません。私はそれに通知を要求する能力を与えるだけで、そして複雑な規則をサガの中に残します。この種のものはサンクや約束で実装するのがかなり難しいです。

notifications

私はこれをsagaでどのようにして行うことができるかについて here を説明しました

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

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

当初、その用語は paper で導入されましたが、sagaパターンは分散トランザクションにおける最終的な一貫性を処理するために使用されることになっていましたが、その使用法はバックエンド開発者によってより広い定義に拡張されました。 「プロセスマネージャ」パターン(どういうわけか元のサガパターンはプロセスマネージャの特殊な形式です)。

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

また見なさい:

代替案

ジェネレータを使うという考えが気に入らず、sagaパターンとそのデカップリング特性に興味があるのなら、まったく同じパターンを表すためにepicという名前を使う redux-observable でも同じことができます。しかし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' });
  }
});
385

簡単な答え :私にとっては非同期問題への完全に合理的なアプローチのようです。いくつか注意してください。

私の仕事で始めたばかりの新しいプロジェクトに取り組むとき、私は非常に似たような考え方をしました。私は、Reactコンポーネントツリーの中身を超えた方法でストアを更新し、コンポーネントを再描画するためのVanilla Reduxのエレガントなシステムを大好きでした。非同期を処理するためのそのエレガントなdispatchメカニズムにフックするのは私には奇妙に思えました。

私は私たちのプロジェクトの外に私が react-redux-controller と呼んだライブラリでそこに持っているものと本当に似たようなアプローチで行きました。

私はあなたが上に持っている正確なアプローチをしないことになったいくつかの理由:

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

まとめると、dispatchとストアをイベントのパラメータと一緒にディスパッチング関数にインジェクトでき​​るようにするためのシステムを準備する必要があります。私はこの依存性注入に対する3つの合理的なアプローチを知っています。

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

更新

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

25
acjay

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

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

  • 減速機 ?とんでもない。それらは副作用のない純粋な関数であるべきです。店を更新することは深刻で複雑な仕事です。それを汚染しないでください。
  • ダムビューコンポーネント? 間違いなく、1つの懸念があります。それは、プレゼンテーションとユーザーとの対話です。できるだけ単純にする必要があります。
  • コンテナ部品? 可能だが、次善の策。コンテナはビュー関連の複雑さをカプセル化してストアと対話する場所であるという点で意味がありますが、
    • コンテナーは、ダムコンポーネントよりも複雑である必要がありますが、それでもやはり単一の責任です:ビューとステート/ストアの間のバインディングを提供すること。あなたの非同期ロジックはそれとは全く別の問題です。
    • コンテナに配置することで、単一のビュー/ルートに対して、非同期ロジックを単一のコンテキストにロックすることになります。悪いアイデア。理想的にはすべて再利用可能で、完全に分離されています。
  • S 他のサービスモジュール? 悪い考え:あなたは店へのアクセスを注入する必要があるでしょう、それは保守性/テスト容易性の悪夢です。提供されているAPI /モデルのみを使用してReduxの粒度に合わせてストアにアクセスするほうがよいでしょう。
  • それらを解釈するアクションとミドルウェア? 何故なの?!まず第一に、それが私たちが残した唯一の主要な選択肢です。 :-)より論理的には、アクションシステムはどこからでも使用できる実行ロジックを分離しています。それは店へのアクセスを得て、そしてより多くの行動を急派することができる。それはアプリケーションのまわりの制御とデータの流れをまとめることである、そしてほとんどの非同期はその中にぴったり収まります。
    • アクションクリエイターはどうですか?アクション自体やミドルウェアではなく、単に非同期をそこで行わないのはなぜですか。
      • まず最も重要なことは、ミドルウェアがそうであるように、クリエイターはストアにアクセスできないということです。つまり、あなたは新しい偶然の行動をディスパッチすることができない、あなたの非同期を構成するためにストアから読むことができない、など。
      • ですから、複雑なものは必要なものに、それ以外はすべて単純なものにしてください。作成者は、テストが容易な単純で比較的純粋な関数になります。
15
XML

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

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

これらのドキュメントはRedux用であり、Redux + React用ではないことに留意してください。 ReduxストアReact componentsにフックアップされたものはあなたが言うとおりにできますが、ミドルウェアのないPlain Jane Reduxストアは普通のオブジェクトを除いてdispatchへの引数を受け付けません。

ミドルウェアがなくても、もちろん可能です

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

しかし、非同期がbyReduxによって処理されるのではなくaroundRedux)にラップされる場合も同様です。したがって、ミドルウェアは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);

これは元のものとそれほど違って見えるわけではありません - それはちょっとシャッフルされています - そしてconnectupdateThingが非同期である(または非同期である必要がある)ことを知りません。

promiseobservablessagas 、または 狂気のカスタム および 非常に宣言的な actionの作成者もサポートしたい場合は、Reduxで単に変更するだけで可能です。あなたはdispatch(別名、アクションクリエイターから返されるもの)に渡します。 Reactコンポーネント(またはconnect呼び出し)を巧みに扱う必要はありません。

11
Michelle Tilley

OK、最初にミドルウェアがどのように動作するかを見てみましょう。それが質問にかなり答えます。これがReduxのpplyMiddleWare関数のソースコードです。

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
      });
    };
  };
}

この部分を見て、dispatchがどのようにfunctionになるかを見てください。

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

さて、これはReduxで最も使われているミドルウェアの一つとしてRedux-thunkが自己紹介している方法です:

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

あなたが見るように、それはアクションではなく関数を返すでしょう、それはあなたがそれが関数であるのであなたが望むときいつでもあなたがそれを待って呼び出すことができることを意味します...

それでは、一体何がサンクなのでしょうか。それがWikipediaで紹介されている方法です。

コンピュータプログラミングでは、さんくは別のサブルーチンに追加の計算を挿入するために使用されるサブルーチンです。サンクは主に、計算が必要になるまで計算を遅らせるため、または他のサブルーチンの先頭または末尾に操作を挿入するために使用されます。それらは、コンパイラコード生成やモジュラープログラミングにおいて、他にもさまざまな用途があります。

この用語は「考える」の派手な派生語として生まれました。

さんくは、評価を遅らせるために式をラップする関数です。

//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;

それで、その概念がどれほど簡単で、非同期アクションを管理するのにどのように役立つかを見てください。

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

Apply middleware Redux

6
Alireza

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'));

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

2
sm chinna

質問に答えるには:

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

少なくとも次の2つの理由があります。

最初の理由は懸念の分離です。action creatorapiを呼び出してデータを取得するのは仕事ではありません。action creator functionに2つの引数を渡す必要があります。 action typeおよびpayload

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

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

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

そして、Redux-Thunk midlewareからdispacheへの仕事は、あなたのapi callの結果から適切なactionへ。

0
Mselmi Ali

同期アクション作成者がいて、次に非同期アクション作成者がいます。

同期アクション作成者とは、それを呼び出すと、そのオブジェクトに関連付けられているすべての関連データとそのレデューサーによる処理の準備が整ったActionオブジェクトをすぐに返すものです。

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

定義上、ネットワーク要求を行うアクション作成者がいる場合はいつでも、非同期アクション作成者としての資格を得ることになります。

もしあなたが非同期アクションクリエータをReduxアプリケーションの中に入れたいのなら、ミドルウェアと呼ばれるものをインストールしなければなりません。それはあなたがそれらの非同期アクションクリエータに対処することを可能にするでしょう。

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

それでは、ミドルウェアとは何ですか。なぜReduxの非同期フローに必要なのでしょうか。

Redux-thunkなどのreduxミドルウェアのコンテキストでは、ミドルウェアは非同期アクション作成者とのやり取りに役立ちます。これは、Reduxがそのままでは処理できないものだからです。

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

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

ミドルウェアは、私たちがディスパッチするすべてのアクションとともに呼び出される単純なJavaScript関数です。その機能の中で、ミドルウェアはアクションが任意のリデューサーにディスパッチされるのを止める機会を持っています。それはアクションを修正するか、あるいは何らかの方法でアクションをいじってしまうことができます。あなたが見る楽しみのためだけにあなたが派遣するあらゆる行動。

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

あなたは、オープンソースミドルウェアを利用したり、依存関係としてそれらをインストールしたりするだけではありません。あなたはあなた自身のカスタムミドルウェアを書いてあなたのReduxストアの中でそれを使うことができます。

ミドルウェアのより一般的な用途の1つ(および回答を得るためのもの)は、非同期アクション作成者を扱うことです。おそらく最も普及しているミドルウェアは、レデュックスサンクであり、非同期アクション作成者の扱いに役立つことです。

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

0
Daniel