はじめに
一昨日に書いたReduxのPresentational ComponentsとContainer Componentsの役割の(個人的な)整理メモの続き的な立ち位置の記事です。
引き続き同じように、今度は http://redux.js.org/docs/advanced/AsyncActions.html の内容となります。
Redux Thunk middleware
Reduxで非同期通信をする場合には、ActionとReducerの間にmiddlewareという形でajaxリクエストさせます。
redux-thunkを(redux本家サイトによるとこちらを標準的に)利用します。
(Thunkと言う言葉自体がプログラミングの概念みたいです。
英Wikipediaでは "In computer programming, a thunk is a subroutine that is created, often automatically, to assist a call to another subroutine." → 『Thunkは(しばしば自動で)生成される、他のサブルーチンを呼ぶための支援的なサブルーチン』となっています。)
middlewareの記述箇所について
middlewareのメインの記述箇所はActionになります。
役割的なことを気にすると、Actionの領域では『何か』が起こった時、Storeに『どんなデータ』を利用するかということを定義するので、ActionにAjaxリクエストのことを記述しちゃっているところが気になりますが、実際に実行されるのはActionとReducerの間のmiddlewareということになるようで、流れの元の方であるAction側で記述しなきゃいけないみたいですね。
redux-thunkを使う
applyMiddleware()関数を使うことによって自分のReduxアプリケーションに適応することが出来ます。
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import { createStore, applyMiddleware } from 'redux'
import { selectSubreddit, fetchPosts } from './actions'
import rootReducer from './reducers'
const loggerMiddleware = createLogger()
const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware // neat middleware that logs actions
)
)
store.dispatch(selectSubreddit('reactjs'))
store.dispatch(fetchPosts('reactjs')).then(() =>
console.log(store.getState())
)
のように適応できます。
※ store.dispatch()についてはこちら
ReduxのAction、Reducer、Storeの(個人的な)整理メモ - Action Creators
Async Actions
前述のようにAction側でMiddleware利用部分を書いていきます。
ReduxでAsyncを使う場合には下記の3つのActionを定義します。
- 要求が開始されたことをReducerに知らせるアクション。
- 要求が正常に終了したことをReducerに通知するアクション。
- 要求が失敗したことをReducerに通知するアクション。
イメージは下記のような感じ
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
または
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
書き方はお好きな方でどうぞ。
Action (fetch関数)
本家サイトの方では利用例は下記のようにfetch()関数を利用することで、Ajaxリクエストを実装しているみたいです。
import fetch from 'isomorphic-fetch'
export const REQUEST_POSTS = 'REQUEST_POSTS'
function requestPosts(subreddit) {
return {
type: REQUEST_POSTS,
subreddit
}
}
// 中略
// 以下、コメントなど削除した例になります
export function fetchPosts(subreddit) {
return function (dispatch) {
dispatch(requestPosts(subreddit))
return fetch(`http://www.reddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json =>
dispatch(receivePosts(subreddit, json))
)
}
}
※ ここで出てくるfetch()関数はjavascriptで実装が進んでいる標準の関数になるようです。ただ、まだ未実装なブラウザが多いので、isomorphic-fetchライブラリを使いましょうとのこと。
あとの書き方(Reducers、Components)は、Synchronous(非同期)なときのAction Creatorと一緒になっていきます。
Reducer
同期のときと一緒ですが、参考になるので本家サイトから引用させて最後に併せて載せさせていただきます。
Reducerで成功した時、失敗したときのpropsの内容を書いていきます。
この場合だとpostsBySubreddit()で全部受けて、投稿関連だけ抽出してpost()に渡して各挙動を振り分けているみたいですね。
import { combineReducers } from 'redux'
import {
SELECT_SUBREDDIT, INVALIDATE_SUBREDDIT,
REQUEST_POSTS, RECEIVE_POSTS
} from './actions'
function selectedSubreddit(state = 'reactjs', action) {
// 略
}
function posts(state = {
isFetching: false,
didInvalidate: false,
items: []
}, action) {
switch (action.type) {
case INVALIDATE_SUBREDDIT:
return Object.assign({}, state, {
didInvalidate: true
})
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
})
default:
return state
}
}
function postsBySubreddit(state = { }, action) {
switch (action.type) {
case INVALIDATE_SUBREDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
return Object.assign({}, state, {
[action.subreddit]: posts(state[action.subreddit], action)
})
default:
return state
}
}
const rootReducer = combineReducers({
postsBySubreddit,
selectedSubreddit
})
export default rootReducer