843

ドキュメントによると、「ミドルウェアがないと、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>;
    }
}

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

Note the update function in the connect call. It dispatches an action that tells the App that it is updating, and then performs an async call. After the call finishes, the provided value is dispatched as a payload of another action.

What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?

EDIT: I searched the Redux repo for clues, and found that Action Creators were required to be pure functions in the past. For example, here's a user trying to provide a better explanation for async data flow:

The action creator itself is still a pure function, but the thunk function it returns doesn't need to be, and it can do our async calls

アクションの作成者は、もはや純粋である必要はありません。ということは、以前は必ず thunk/promise ミドルウェアが必要だったのですが、そうではなくなったということでしょうか。

4

11 に答える 11

866

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

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

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

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 Middleware を使用すると、次のように記述できます。

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

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

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

手がかりを求めて Redux リポジトリを検索したところ、以前はアクション クリエーターは純粋な関数である必要があったことがわかりました。

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

于 2016-01-04T20:50:12.887 に答える
15

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

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

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

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

Redux Thunk ミドルウェアを使用すると、アクションの代わりに関数を返すアクション クリエーターを作成できます。サンクを使用して、アクションのディスパッチを遅らせたり、特定の条件が満たされた場合にのみディスパッチしたりできます。内部関数はストア メソッドのディスパッチと 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;

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

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

ミドルウェアReduxを適用

于 2017-11-18T12:22:47.430 に答える