[解決済み] Reduxの非同期フローになぜミドルウェアが必要なのか?
質問
ドキュメントによると
ミドルウェアを使用しない場合、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
を呼び出します。これは、Appに更新中であることを伝えるアクションをディスパッチし、非同期呼び出しを実行します。呼び出しが終了すると、提供された値は、別のアクションのペイロードとしてディスパッチされます。
この方法のどこが問題なのでしょうか?ドキュメントにあるように、なぜRedux ThunkやRedux Promiseを使いたがるのでしょうか?
EDITです。 Reduxのレポを検索して手がかりを探したところ、Action Creatorは過去に純粋な関数であることが要求されていたことがわかりました。例えば 非同期データフローについて、より良い説明を提供しようとしているユーザーです。
<ブロッククオートアクション・クリエーター自体はまだ純粋な関数ですが、それが返すサンク関数はその必要がなく、私たちの非同期呼び出しを行うことができます。
アクションクリエイターは純粋である必要がなくなりました。 つまり、thunk/promiseミドルウェアは、以前は間違いなく必要でしたが、現在はそうではなくなっているようですね?
どうすればいい?
<ブロッククオートこの方法のどこが問題なのでしょうか?ドキュメントにあるように、なぜRedux ThunkやRedux Promiseを使いたがるのでしょうか?
この方法は何も間違っていません。ただ、大規模なアプリケーションでは不便です。なぜなら、異なるコンポーネントが同じアクションを実行したり、いくつかのアクションをデバウンスしたり、自動インクリメントのIDなどのローカルステートをアクションクリエイターの近くに保持したりしたい場合があるからです。そのため、アクションクリエイターを別の関数に抽出する方がメンテナンスの観点からは簡単なのです。
を読むことができます。 Reduxのアクションをタイムアウトでディスパッチする方法」に対する私の回答です。 を使うと、より詳細なチュートリアルができます。
Redux ThunkやRedux Promiseのようなミドルウェアは、Thunkや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
}
つまり、大きな違いはないのです。後者のアプローチで気に入っていることのひとつは、コンポーネントがアクションの作成者が非同期であることを気にしないことです。それはただ
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の状態を後で調べさせたい場合、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アプリにおける非同期リクエストの可能なアプローチのひとつに過ぎません。もうひとつの興味深いアプローチは リダックス佐賀 このデーモンでは、アクションが来るとそれを受け取り、アクションを出力する前にリクエストを変換または実行する、長時間稼働するデーモン (「サーガ」) を定義できます。これはロジックをアクションクリエーターからサーガに移行させるものです。一度チェックしてみて、自分に合ったものを選ぶといいかもしれません。
<ブロッククオートReduxのレポで手がかりを探したところ、Action Creatorは過去に純粋な関数であることが要求されていたことがわかりました。
これは不正確です。docsにはこう書いてありましたが、docsは間違っていました。
アクションクリエイターは、純粋な関数であることを要求されたことはありません。
それを反映させるためにドキュメントを修正しました。
関連
-
要素ツリー制御によるvueTreeテーブル
-
HTML+CSS+JavaScriptで簡単な三目並べゲームを作成する。
-
vue for 登録ページ効果 vue for sms 認証コードログイン
-
vueディレクティブv-bindの使用と注意点
-
[解決済み】React - uncaught TypeError: 未定義のプロパティ 'setState' を読み取れない
-
[解決済み】TypeErrorの解決方法。未定義またはヌルをオブジェクトに変換できない
-
[解決済み] 配列の反復処理に "for...in "を使用するのは、なぜ良くないのでしょうか?
-
[解決済み] ES6 ジェネレータで redux-saga を使用する利点/欠点と ES2017 async/await で redux-thunk を使用する利点/欠点
-
[解決済み】Redux - 複数店舗、なぜダメなの?
-
[解決済み】React-ReduxとmapStateToProps()を理解する。)
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
元のイベントが実行されなかった後に要素を追加するためのjQueryソリューション
-
JavaScriptの配列共通メソッド解説
-
[解決済み】Uncaught SyntaxError: JSONの位置0に予期しないトークンuがあります。
-
[解決済み】"フォームが接続されていないため、フォームの送信がキャンセルされました "というエラーの取得について
-
[解決済み】JavaScript TypeError: null のプロパティ 'style' を読み取ることができない
-
[解決済み】ReactJSでエラー発生 Uncaught TypeError: Super expression は null か関数でなければならず、undefined ではありません。
-
OSSアップロードエラーを解決する: net::ERR_SSL_PROTOCOL_ERROR
-
[解決済み] Reduxのアクションをタイムアウトでディスパッチする方法とは?
-
[解決済み] ES6 ジェネレータで redux-saga を使用する利点/欠点と ES2017 async/await で redux-thunk を使用する利点/欠点
-
[解決済み】ReactJS 2つのコンポーネントが通信しています。