Slide 1

Slide 1 text

React Native でRedux‑Saga を使ったりした 話 Tokyo Otaku Mode Internal Developers Sesssion Aug 25, 2017 Hiroshi HORIKI @pchw

Slide 2

Slide 2 text

React Native で最近作ったアプリたち

Slide 3

Slide 3 text

React Native React の技術を使って,Native アプリを作れる シンプルなアプリを作るだけなら, 特にフレー ムワー クを入れる必要な く作れる.

Slide 4

Slide 4 text

アー キテクチャ導入の決定 「 アプリが複雑になることが決まっている」 「 そろそろ状態管理が辛くなってきた」 「 非同期の処理が多くなってきて, 特定のstate をどこの処理が変更 しているかわからなくなってきた」

Slide 5

Slide 5 text

Flux アー キテクチャ

Slide 6

Slide 6 text

Redux Flux 実装の1 つ 1 方向のデー タの流れ グロー バルな一つのImmutable State 上図のような概念を導入できる.

Slide 7

Slide 7 text

Redux 実装者が用意する必要があるのは, Action の一覧 Reducer というAction の種類によってState を更新し新しいState を 生成する処理 初期State の値 View であるComponent State を変更したい時にdispatch でAction を送出するようにする

Slide 8

Slide 8 text

Redux import { createStore } from 'redux'; import reducers from './reducers' const initialState = {...}; const store = createStore( reducers, initialState ); のようにして,Reducer と初期State の値を元にStore を作る. store.dispatch() とすれば, reducers がハンドリングして新しい State が生成されてStore の中身が更新される.

Slide 9

Slide 9 text

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)

Slide 10

Slide 10 text

React‑Redux トップレベルのComponent は特別に Provider というComponent で囲 んであげて, createStore で生成したstore をプロパティとして渡す必 要があります. import { Provider } from 'react‑redux'; import Hoge from './HogeComponent' export default class App extends Component { render() { return ( ); } }

Slide 11

Slide 11 text

( やっと) Redux‑Saga の話

Slide 12

Slide 12 text

Redux‑Saga API 連携とかの機能が付いてくると, 非同期で処理を行い, その結果をま た別のAPI に投げて・・・ ということがあり, そこで導入すると良いの が Redux‑Saga . Reducer Action で投げられたタイプによってState を変えて新しいState を作り出す 複雑な処理は行ってはならない Redux‑Saga Action で投げられたタイプによって処理を行い副作用を起こす 副作用をStore に反映するのはdispatch 経由.

Slide 13

Slide 13 text

Redux‑Saga

Slide 14

Slide 14 text

Redux‑Saga Action の待受は yield takeEvery(ACTION_TYPE_XXX, doSomethingX); yield takeEvery(ACTION_TYPE_YYY, doSomethingY); yield takeEvery(ACTION_TYPE_ZZZ, doSomethingZ); みたいにする.

Slide 15

Slide 15 text

Redux‑Saga は何が嬉しいのか? generator を活用して書くため, 全て同期的に書ける 並行してのAction の待ち受けや,2 回目にAction が飛んできたら別 の処理をしたいということも簡単. 処理と処理を完全に分離して書ける API の呼び出しやState の取得・ 変更もRedux‑Saga のgenerator 関 数を経由して書くため, 処理をモックすることなくテストを簡潔に 書くことが出来る

Slide 16

Slide 16 text

Redux‑Saga の世界 redux‑saga/effects に色んなsaga の世界観を実現するためのAPI が 用意されている take fork select call put を覚えておけば, 基本的なケー スに対応できる.

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Redux‑Saga を使う準備 redux‑saga/effects から使うものをimport する. import { take, fork, select, call, put, takeEvery, takeLatest} from 'redux‑saga/effects'

Slide 19

Slide 19 text

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 が始まる) ことができる.

Slide 20

Slide 20 text

fork: 平行世界を作る 前述の take だけだと, 並行してAction を待ち受けず, 処理している間 にAction を取りこぼしたりする. そこで, fork を使う.

Slide 21

Slide 21 text

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 も待ち受ける. これで, ステー ジの開始のアクションもハンドリング出来るし, 並行し て乱入者が来たかどうかをハンドリングすることも出来る.

Slide 22

Slide 22 text

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); と書くだけで実現できる.

Slide 23

Slide 23 text

takeLatest 検索バー のインクリメンタル検索のように, 次の入力があったら最後の 入力だけで処理をしたいというときに使えるのが takeLatest yield takeLatest( ACTION_TYPE_SEARCH_BAR_CHANGED,searchProduct); イベントのたびに searchProduct が呼ばれる. 処理中に同じAction が再度飛んできた場合, 前回のタスクをキャンセル して新しく searchProduct を実行する. takeEvery と同様に, fork 作用と while で継続的に待ち受ける機 能も有しているので, 上記のように書くだけで別のAction の待受と並行 で待ち受けることが出来る.

Slide 24

Slide 24 text

select: Store からstate を読み出す take して実行している関数の中で Store に格納された state が使い たい場合は select を使って読み出すことが出来る. const searchProduct = function*(){ const state = yield select(); }

Slide 25

Slide 25 text

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 すれば テストができる.

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

図にまとめると

Slide 28

Slide 28 text

まとめ Redux‑Saga を使うと非同期のややこしい処理が分割出来て同期 的に書けて捗る React Native でも使える 仕組み的には,redux のvue 版であるVuex などにも応用出来そう?