Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Nativeで作ったアプリでRedux-Sagaを使ったので,その話 / TOM ...

pochi
September 01, 2017

React Nativeで作ったアプリでRedux-Sagaを使ったので,その話 / TOM Internal Developer Session #7

Redux-Sagaの説明周り

pochi

September 01, 2017
Tweet

More Decks by pochi

Other Decks in Programming

Transcript

  1. Redux 実装者が用意する必要があるのは, Action の一覧 Reducer というAction の種類によってState を更新し新しいState を 生成する処理

    初期State の値 View であるComponent State を変更したい時にdispatch でAction を送出するようにする
  2. Redux import { createStore } from 'redux'; import reducers from

    './reducers' const initialState = {...}; const store = createStore( reducers, initialState ); のようにして,Reducer と初期State の値を元にStore を作る. store.dispatch() とすれば, reducers がハンドリングして新しい State が生成されてStore の中身が更新される.
  3. React‑Redux react‑redux というモジュー ルを入れることで, View である Component に Store へのアクセスやAction

    をdispatch するための仕組 みを導入することができる. import { connect } from 'react‑redux'; class HogeComponent extends React.Component { render(){ const { somethingStateField, dispatch } = this.props; dispatch({type: ACTION_TYPES.HOGE_RENDERED}); } } export default connect(mapStateToProps)(HogeComponent)
  4. React‑Redux トップレベルのComponent は特別に Provider というComponent で囲 んであげて, createStore で生成したstore をプロパティとして渡す必

    要があります. import { Provider } from 'react‑redux'; import Hoge from './HogeComponent' export default class App extends Component { render() { return ( <Provider store={store}> <Hoge/> </Provider> ); } }
  5. Redux‑Saga API 連携とかの機能が付いてくると, 非同期で処理を行い, その結果をま た別のAPI に投げて・・・ ということがあり, そこで導入すると良いの が

    Redux‑Saga . Reducer Action で投げられたタイプによってState を変えて新しいState を作り出す 複雑な処理は行ってはならない Redux‑Saga Action で投げられたタイプによって処理を行い副作用を起こす 副作用をStore に反映するのはdispatch 経由.
  6. Redux‑Saga は何が嬉しいのか? generator を活用して書くため, 全て同期的に書ける 並行してのAction の待ち受けや,2 回目にAction が飛んできたら別 の処理をしたいということも簡単.

    処理と処理を完全に分離して書ける API の呼び出しやState の取得・ 変更もRedux‑Saga のgenerator 関 数を経由して書くため, 処理をモックすることなくテストを簡潔に 書くことが出来る
  7. Redux‑Saga を使う準備 Redux 時代のコー ドから createStore のところを変更 import { createStore,

    applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux‑saga'; import rootSaga from './sagas'; const sagaMiddleware = createSagaMiddleware(); const store = createStore( reducers, initialState, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(rootSaga);
  8. take: Action の待受 yield take(ACTION_TYPE_XXXX, doSomethingX); ACTION_TYPE_XXX を待ち受けて, doSomethingX を実行する.

    待ち受けたり, 実行してる間には, 他のイベントを取りこぼす. 順次処理を行う時は, yield take(ACTION_TYPE_START_STAGE, startStageOne); yield take(ACTION_TYPE_START_STAGE, startStageTwo); みたいにすると,1 つ目の take が終わったら2 つ目の take で待受にな るので, 同じイベントでも2 回目で処理を変える( この場合はステー ジ2 が始まる) ことができる.
  9. fork: 平行世界を作る yield fork(function*(){ yield take(ACTION_TYPE_START_STAGE, startStageOne); }) yield fork(function*(){

    yield take(ACTION_TYPE_ENCOUNT_CHALLENGER, encountChallenger); }) のようにすると, yield fork はすぐに制御が返ってきて, 裏では指定 した関数が実行される. 1 つめの fork で ACTION_TYPE_START_STAGE を待ち受ける. fork はすぐに制御を返すので, そのまま2 つめの fork へ進 み, ACTION_TYPE_ENCOUNT_CHALLENGER も待ち受ける. これで, ステー ジの開始のアクションもハンドリング出来るし, 並行し て乱入者が来たかどうかをハンドリングすることも出来る.
  10. takeEvery take は1 度Action を受け取ったらそれで終わり. タップ入力などの何度もAction をハンドリングする必要が有る場合は yield fork(function*(){ while(true){

    yield take(ACTION_TYPE_MOVE_LEFT, moveLeft); } }); のように,while ルー プで何度も待ち受けるというようなことをしないと いけない. Redux‑Saga はヘルパー を用意していて, 上記と同様の fork してwhile ルー プで待ち受けるような処理を yield takeEvery(ACTION_TYPE_MOVE_LEFT, moveLeft); と書くだけで実現できる.
  11. takeLatest 検索バー のインクリメンタル検索のように, 次の入力があったら最後の 入力だけで処理をしたいというときに使えるのが takeLatest yield takeLatest( ACTION_TYPE_SEARCH_BAR_CHANGED,searchProduct); イベントのたびに

    searchProduct が呼ばれる. 処理中に同じAction が再度飛んできた場合, 前回のタスクをキャンセル して新しく searchProduct を実行する. takeEvery と同様に, fork 作用と while で継続的に待ち受ける機 能も有しているので, 上記のように書くだけで別のAction の待受と並行 で待ち受けることが出来る.
  12. select: Store からstate を読み出す take して実行している関数の中で Store に格納された state が使い

    たい場合は select を使って読み出すことが出来る. const searchProduct = function*(){ const state = yield select(); }
  13. call: 非同期の関数を呼び出す 外部にアクセスするAPI などを叩く時などは call を使う const searchProduct = function*(){

    const state = yield select(); const result = yield call( API.findProduct, {q: state.searchText}); } テストの簡便さのために, yield API.findProduct({q: state.searchText}) ではなく, call で囲んで呼ぶ. call を経由しないで行う場合は, テスト時に API.findProduct が返 すPromise がどのような状態だとテストがPass なのかが難しく, 外部に アクセスする場合はAPI をモックが必要 call を使うと, タスクのオブジェクトが返り, それを assert すれば テストができる.
  14. put タスク内で得られた結果を Store に反映させたい場合は put を使っ て,Action をdispatch する. 新しいState

    を作成し,Store を変更するのはReducer の責務. const searchProduct = function*(){ const state = yield select(); const result = yield call( API.findProduct, {q: state.searchText}); yield put({ type: ACTION_TYPE_SEARCH_COMPLETE, payload:{products: result.products} }); }