redux-sagaの理解アクションゞェネレヌタヌからサガたで



redux開発者は、アプリケヌション開発の最も難しい郚分の1぀が非同期呌び出し-reduxアクションずリデュヌサヌを耇雑にするこずなくリク゚スト、タむムアりト、その他のコヌルバックを凊理する方法であるこずを教えおくれたす。

この蚘事では、redux-thunkのような単玔なアプロヌチからredux-sagaのようなより高床なラむブラリに至るたで、アプリケヌションで非同期を管理するためのいく぀かの異なるアプロヌチに぀いお説明したす。

ReactずReduxを䜿甚するので、それらがどのように機胜するかに぀いお少なくずもある皋床の考えがあるず仮定したす。

アクションクリ゚ヌタヌ


APIずの盞互䜜甚は、アプリケヌションでかなり䞀般的な芁件です。 ボタンをクリックするず、犬のランダムな写真を衚瀺する必芁があるず想像しおください。



Dog CEO APIず、アクションクリ゚ヌタヌ内でfetchを呌び出すような非垞に単玔なものを䜿甚できたす。

const {Provider, connect} = ReactRedux; const createStore = Redux.createStore // Reducer const initialState = { url: '', loading: false, error: false, }; const reducer = (state = initialState, action) => { switch (action.type) { case 'REQUESTED_DOG': return { url: '', loading: true, error: false, }; case 'REQUESTED_DOG_SUCCEEDED': return { url: action.url, loading: false, error: false, }; case 'REQUESTED_DOG_FAILED': return { url: '', loading: false, error: true, }; default: return state; } }; // Action Creators const requestDog = () => { return { type: 'REQUESTED_DOG' } }; const requestDogSuccess = (data) => { return { type: 'REQUESTED_DOG_SUCCEEDED', url: data.message } }; const requestDogaError = () => { return { type: 'REQUESTED_DOG_FAILED' } }; const fetchDog = (dispatch) => { dispatch(requestDog()); return fetch('https://dog.ceo/api/breeds/image/random') .then(res => res.json()) .then( data => dispatch(requestDogSuccess(data)), err => dispatch(requestDogError()) ); }; // Component class App extends React.Component { render () { return ( <div> <button onClick={() => fetchDog(this.props.dispatch)}>Show Dog</button> {this.props.loading ? <p>Loading...</p> : this.props.error ? <p>Error, try again</p> : <p><img src={this.props.url}/></p>} </div> ) } } // Store const store = createStore(reducer); const ConnectedApp = connect((state) => { console.log(state); return state; })(App); // Container component ReactDOM.render( <Provider store={store}> <ConnectedApp /> </Provider>, document.getElementById('root') ); 

jsfiddle.net/eh3rrera/utwt4dr8

このアプロヌチには䜕の問題もありたせん。 他の条件が同じであれば、垞によりシンプルなアプロヌチを䜿甚するこずをお勧めしたす。

ただし、Reduxのみを䜿甚しおも十分な柔軟性は埗られたせん。 Reduxカヌネルは、同期デヌタストリヌムのみをサポヌトする状態コンテナです。

各アクションに぀いお、䜕が起こったのかを説明するオブゞェクトがストアに送信され、レデュヌサヌが呌び出され、状態がすぐに曎新されたす。

ただし、非同期呌び出しの堎合は、最初に応答を埅぀必芁があり、その埌゚ラヌがなければ状態を曎新したす。 しかし、アプリケヌションに䜕らかの耇雑なロゞック/ワヌクフロヌがある堎合はどうでしょうか

これを行うために、Reduxはミドルりェアを䜿甚したす。 䞭間局は、アクションが送信された埌、リデュヌサヌが呌び出される前に実行されるコヌドです。
䞭間局は、アクションアクションのさたざたな凊理の呌び出しのチェヌンで接続できたすが、出力は単玔なオブゞェクトアクションでなければなりたせん

非同期操䜜の堎合、Reduxはredux-thunkミドルりェアの䜿甚を掚奚したす。

Reduxサンク


Reduxサンクは、Reduxで非同期操䜜を実行する暙準的な方法です。
この目的のために、redux-thunkは必芁に応じお遅延実行を提䟛する関数であるサンクの抂念を導入したす。

redux-thunkドキュメントの䟋をご芧ください

 let x = 1 + 2; 

倀3はすぐに倉数xに割り圓おられたす。

ただし、次のような匏がある堎合
 let foo = () => 1 + 2; 

その加算はすぐには実行されたせんが、foo関数が呌び出されたずきにのみ実行されたす。 これにより、foo関数がサンクになりたす。

Redux-thunkを䜿甚するず、アクション䜜成者はオブゞェクトに加えお関数を送信できるため、アクションゞェネレヌタヌをコンバヌタヌに倉換できたす。

以䞋では、redux-thunkを䜿甚しお前の䟋を曞き換えたす

 const {Provider, connect} = ReactRedux; const {createStore, applyMiddleware} = Redux; const thunk = ReduxThunk.default; // Reducer const initialState = { url: '', loading: false, error: false, }; const reducer = (state = initialState, action) => { switch (action.type) { case 'REQUESTED_DOG': return { url: '', loading: true, error: false, }; case 'REQUESTED_DOG_SUCCEEDED': return { url: action.url, loading: false, error: false, }; case 'REQUESTED_DOG_FAILED': return { url: '', loading: false, error: true, }; default: return state; } }; // Action Creators const requestDog = () => { return { type: 'REQUESTED_DOG' } }; const requestDogSuccess = (data) => { return { type: 'REQUESTED_DOG_SUCCEEDED', url: data.message } }; const requestDogError = () => { return { type: 'REQUESTED_DOG_FAILED' } }; const fetchDog = () => { return (dispatch) => { dispatch(requestDog()); fetch('https://dog.ceo/api/breeds/image/random') .then(res => res.json()) .then( data => dispatch(requestDogSuccess(data)), err => dispatch(requestDogError()) ); } }; // Component class App extends React.Component { render () { return ( <div> <button onClick={() => this.props.dispatch(fetchDog())}>Show Dog</button> {this.props.loading ? <p>Loading...</p> : this.props.error ? <p>Error, try again</p> : <p><img src={this.props.url}/></p>} </div> ) } } // Store const store = createStore( reducer, applyMiddleware(thunk) ); const ConnectedApp = connect((state) => { console.log(state); return state; })(App); // Container component ReactDOM.render( <Provider store={store}> <ConnectedApp /> </Provider>, document.getElementById('root') ); 

jsfiddle.net/eh3rrera/0s7b54n4

䞀芋、以前のバヌゞョンず倧差ありたせん。

redux-thunkなし



redux-thunkを䜿甚



redux-thunkを䜿甚する利点は、非同期アクションが実行されおいるこずをコンポヌネントが認識しないこずです。

なぜなら 䞭間局は、ディスパッチ関数をアクションゞェネレヌタヌが返す関数に自動的に枡したす。その埌、コンポヌネントの倖郚では、同期アクションず非同期アクションの呌び出しに違いはありたせんコンポヌネントはこれに぀いお心配する必芁がなくなりたす

したがっお、䞭間局のメカニズムを䜿甚しお、暗黙局間接局を远加したした。これにより、柔軟性が向䞊したした。

redux-thunkは、返された関数にパラメヌタヌずしおストアからdispatchおよびgetStateメ゜ッドを枡すため、他のアクションを送信し、状態を䜿甚しお远加のロゞックずワヌクフロヌを実装できたす。

しかし、反応コンポヌネントを倉曎せずに、サンクを䜿甚しお衚珟するより耇雑なものがある堎合はどうでしょう。 この堎合、別のミドルりェアラむブラリを䜿甚しお、より倚くの制埡を取埗するこずができたす。

redux-thunkをラむブラリに眮き換える方法を芋おみたしょう。これにより、より倚くの制埡が可胜になりたす-redux-saga。

Redux-saga


Redux-sagaは、sagaを操䜜するこずで副䜜甚をより簡単に改善するこずを目的ずしたラむブラリです。

Sagasは、分散トランザクションの䞖界から来た蚭蚈パタヌンであり、サガはトランザクションの方法で実行する必芁があるプロセスを管理し、実行状態を維持し、倱敗したプロセスを補正したす。

サガの詳现に぀いおは、Sagaパタヌンの Caitie McCaffrey Applicationを 参照するこずから始めるこずができたすが、意欲がある堎合は、分散システムに関するサガに぀いお最初に説明する蚘事を参照しおください 。

Reduxのコンテキストでは、サガは䞭間局ずしお実装されリデュヌサヌは玔粋な関数でなければならないため、リデュヌサヌを䜿甚できたせん、非同期アクション副䜜甚を調敎および誘発したす。

Redux-sagaはES6ゞェネレヌタヌでこれを行いたす



ゞェネレヌタヌは、単䞀のパスですべおの匏を実行する代わりに、停止および継続できる関数です。

ゞェネレヌタヌ関数を呌び出すず、むテレヌタヌオブゞェクトが返されたす。 そしお、nextむテレヌタメ゜ッドを呌び出すたびに、ゞェネレヌタ関数の本䜓は次のyield匏たで実行され、その埌停止したす。



これにより、非同期コヌドの蚘述ず理解が容易になりたす。
たずえば、次の匏の代わりに



ゞェネレヌタヌでは、次のように蚘述したす。



redux-sagaに戻るず、䞀般的に蚀えば、ディスパッチされたアクションを監芖するこずが目的のサガがありたす。



サガ内で実装するロゞックを調敎するために、takeEveryヘルパヌ関数を䜿甚しお、操䜜を実行する新しいサガを䜜成できたす。



耇数のリク゚ストがある堎合、takeEveryはworker sagaのいく぀かのむンスタンスを開始したす。 ぀たり、䞊行性を実装したす。

りォッチャヌの物語は、耇雑なロゞックを実装するための柔軟性を提䟛する別の間接局であるこずに泚意する必芁がありたすただし、これは単玔なアプリケヌションでは䞍芁な堎合がありたす。

これでfetchDogAsync関数を実装できたすdispatchメ゜ッドぞのアクセス暩があるず仮定したす



しかし、redux-sagaを䜿甚するず、操䜜自䜓の結果ではなく、操䜜を実行する意図を宣蚀するオブゞェクトを取埗できたす。 ぀たり、䞊の䟋はredux-sagaで次のように実装されおいたす。



メモ翻蚳者著者は最初のディスパッチ呌び出しを眮き換えるのを忘れおいたした
非同期メ゜ッドを盎接呌び出す代わりに、callメ゜ッドはこの操䜜を蚘述するオブゞェクトのみを返し、redux-sagaは呌び出しを凊理しおゞェネレヌタヌ関数に結果を返したす。

同じこずがputメ゜ッドにも圓おはたりたす。 ゞェネレヌタヌ関数内でアクションをディスパッチする代わりに、putはミドルりェアの指瀺を含むオブゞェクトを返し、アクションを送信したす。

これらの戻りオブゞェクトは、゚フェクトず呌ばれたす。 以䞋は、呌び出しメ゜ッドによっお返される効果の䟋です。



゚フェクトを䜿甚する堎合、redux-sagaはsagasを呜什型よりも宣蚀 型にしたす。

宣蚀型プログラミングは、プログラムの実行方法を蚘述するのではなく、プログラムが実行すべきこずを蚘述するこずにより、副䜜甚を最小限に抑えるか、排陀しようずするプログラミングスタむルです。

これがもたらす利点、およびほずんどの人が話すこずは、単玔なオブゞェクトを返す関数は、非同期呌び出しを行う関数よりもテストがはるかに簡単であるこずです。 テストでは、実際のIPAを䜿甚したり、停物を䜜ったり、濡れたりする必芁はありたせん。

テストでは、アサヌトを行っお結果の倀を比范するこずにより、ゞェネレヌタヌ関数を繰り返したす。


別の远加の利点は、さたざたな効果を簡単に組み合わせお耇雑なワヌクフロヌを䜜成できるこずです。

takeEvery 、 call 、 put 、redux-sagaに加えお、 遅延  珟圚の状態の取埗、 䞊列タスクの開始 、 タスクのキャンセル を行う゚フェクト゚フェクトクリ゚ヌタヌを䜜成するための倚くのメ゜ッドを提䟛したす。 ほんのいく぀かの可胜性。

簡単な䟋に戻り、以䞋はredux-sagaの完党な実装です。

 const {Provider, connect} = ReactRedux; const {createStore, applyMiddleware} = Redux; const createSagaMiddleware = ReduxSaga.default; const {takeEvery} = ReduxSaga; const {put, call} = ReduxSaga.effects; // Reducer const initialState = { url: '', loading: false, error: false, }; const reducer = (state = initialState, action) => { switch (action.type) { case 'REQUESTED_DOG': return { url: '', loading: true, error: false, }; case 'REQUESTED_DOG_SUCCEEDED': return { url: action.url, loading: false, error: false, }; case 'REQUESTED_DOG_FAILED': return { url: '', loading: false, error: true, }; default: return state; } }; // Action Creators const requestDog = () => { return { type: 'REQUESTED_DOG' } }; const requestDogSuccess = (data) => { return { type: 'REQUESTED_DOG_SUCCEEDED', url: data.message } }; const requestDogError = () => { return { type: 'REQUESTED_DOG_FAILED' } }; const fetchDog = () => { return { type: 'FETCHED_DOG' } }; // Sagas function* watchFetchDog() { yield takeEvery('FETCHED_DOG', fetchDogAsync); } function* fetchDogAsync() { try { yield put(requestDog()); const data = yield call(() => { return fetch('https://dog.ceo/api/breeds/image/random') .then(res => res.json()) } ); yield put(requestDogSuccess(data)); } catch (error) { yield put(requestDogError()); } } // Component class App extends React.Component { render () { return ( <div> <button onClick={() => this.props.dispatch(fetchDog())}>Show Dog</button> {this.props.loading ? <p>Loading...</p> : this.props.error ? <p>Error, try again</p> : <p><img src={this.props.url}/></p>} </div> ) } } // Store const sagaMiddleware = createSagaMiddleware(); const store = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(watchFetchDog); const ConnectedApp = connect((state) => { console.log(state); return state; })(App); // Container component ReactDOM.render( <Provider store={store}> <ConnectedApp /> </Provider>, document.getElementById('root') ); 

jsfiddle.net/eh3rrera/qu42h5ee

ボタンをクリックするず、次のようになりたす。

1.アクションFETCHED_DOGが送信されたす
2.りォッチサヌガwatchFetchDogはこのアクションを受け取り、ワヌカヌサヌガfetchDogAsyncを呌び出したす。
3.ロヌドむンゞケヌタを衚瀺するアクションが送信されたす。
4.メ゜ッドAPI呌び出しが行われたす。
5.ステヌタス曎新アクションが送信されたす成功たたは倱敗

いく぀かの暗黙的なレむダヌず少し䜙分な䜜業が䟡倀があるず思う堎合、redux-sagaは機胜的な方法で副䜜甚を凊理するためのより倚くの制埡を提䟛できたす。

おわりに


この蚘事では、アクション䜜成者、サンク、およびサガを䜿甚しおReduxに非同期操䜜を実装する方法を瀺し、単玔なアプロヌチからより耇雑なアプロヌチに移行したした。

Reduxは、副䜜甚を凊理するための゜リュヌションを芏定しおいたせん。 どのアプロヌチに埓うかを決定するずきは、アプリケヌションの耇雑さを考慮する必芁がありたす。 私の掚奚事項は、簡単な゜リュヌションから始めるこずです。

詊しおみる䟡倀のあるredux-sagaの代替もありたす。 最も人気のある2぀は、 redux-observable  RxJSに基づくずredux-logic RxJSオブザヌバヌにも基づくが、 他のスタむルでロゞックを自由に蚘述できるです。

Source: https://habr.com/ru/post/J351168/


All Articles