react-router-redux 4.0.0 向けに新しいのを書きました
-- 以下は古いもの --
Redux way では描画に必要なデータは props 経由で渡すことになります。 画面遷移時にデータの読込どうしようかと少し悩みましたが、今のところ、URLの変更を検知して、そのURLに対応したAPIを叩くという方向で進めています。
React Router と Redux の繋ぎとして react-router-redux を使っていまして、今のところ以下のような感じです。
import { createStore, applyMiddleware, combineReducers } from 'redux'; import thunkMiddleware from 'redux-thunk'; import createLogger from 'redux-logger'; import { syncHistory, TRANSITION, UPDATE_LOCATION } from 'react-router-redux'; import reducer from 'reducers'; import { loading, updateData } from 'actions/common'; import findAPI from 'apis'; const dataFetchMiddleware = store => next => { // Action type for initial load const INITIAL_LOAD = 'INITIAL_LOAD'; // Utility method for asynchronous action dispatch function dispatchAsync(dispatch, action) { Promise.resolve(action).then(dispatch); return action; } // Utility method for API call function updateDataFromAPI(location) { // Call API findAPI(location, store)(data => { store.dispatch(updateData(data)); next(loading(false)); }); } // Dispatch initial action asynchronously dispatchAsync(store.dispatch, {type:INITIAL_LOAD, payload:location}); // Return core middleware function return action => { // Handle first screen load if (action.type === INITIAL_LOAD) { updateDataFromAPI(action.payload); return next(action); } // Activate loading screen before transition if (action.type === TRANSITION) { // This loading action is for user's action next(loading(true)); // Dispatch original action to next middleware asynchronously to freeze screen before transition return dispatchAsync(next, action); } // Fetch data on update location if (action.type === UPDATE_LOCATION) { // This loading action is for browser's history action next(loading(true)); updateDataFromAPI(action.payload); // Dispatch original action to next middleware asynchronously to freeze screen before transition return dispatchAsync(next, action); } return next(action); }; } export default function configureStore(initialState, history) { const loggerMiddleware = createLogger(); const reduxRouterMiddleware = syncHistory(history); return createStore(reducer, initialState, applyMiddleware(thunkMiddleware, loggerMiddleware, dataFetchMiddleware, reduxRouterMiddleware)); }
store生成呼び出しは以下のようになってます。
import createBrowserHistory from 'history/lib/createBrowserHistory'; import configureStore from 'configure-store'; const browserHistory = createBrowserHistory(); const store = configureStore(initialState, browserHistory);
push
等のアクションからURL遷移する場合は、 TRANSITION
が処理された後に UPDATE_LOCATION
が処理されます。このとき React Router は最初の TRANSITION
アクションでルーティングを変更してしまうようなので、TRANSITION
が処理される前にローディング処理のアクションを投げています。
ブラウザの履歴操作でURL遷移する場合は TRANSITION
が無く UPDATE_LOCATION
だけが処理されます。そのため UPDATE_LOCATION
が処理される前にもローディング処理を入れています。
loading(loadingStatus)
は {type:'DISPLAY_LOADING', value:bool}
といったアクションを返すメソッドです。最終的にこいつを処理する reducer がローディング中を示す state の値を書き換えます。ローディング中は props が変更されても画面が書き換わらないように、上っ面の shouldComponentUpdate
でごにょごにょしてます。
ローディング処理の呼び出し後にURL遷移のアクション呼び出しを dispatchAsync(next, action);
とかやっているのは、ローディングのアクションを処理してから先に一度レンダリング処理をして、画面をフリーズさせたかったからです。普通に呼び出すと、両方が処理されてからレンダリングが発生するので、データが無い状態で遷移先の画面が描画されてしまいます。
新規の生成したアクションの呼び出しが所によって next
だったり store.dispatch
だったりするのは、このミドルウェアの上にロガーがいるので、そいつに出すとうざいなと思った物は next
に渡しているという次第です。ミドルウェアの順序重要。
最初の方で dispatchAsync(store.dispatch, {type:INITIAL_LOAD, payload:location});
と呼び出しているのは、一番最初のデータ取得処理をキックするためです。なんか汚い感じがしますが、とりあえず動いてます。