react-reduxのHMRできた!

ここ連日の続き、完結です。

react-router v4での完全再描画を伴わない、変更箇所だけ表示が更新されるHMR(Hot Module Replace)ができました。理由はreact-reduxのProviderコンポーネントの置き場所。連日のアプリケーションが以下のように変わって完成。設定などはこれまでの設定から変えません。

// App.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { createBrowserHistory } from 'history';
import { Provider } from 'react-redux';
import { AppContainer } from 'react-hot-loader';

import Store from './Store'; // これを機にcreateStoreをまとめて外だし
import Root from './Root';

injectTapEventPlugin();
const history = createBrowserHistory();

// ProviderとAppContainerの位置関係が大事。Providerが外、AppContainerが内。
const render = () => {
    ReactDOM.render(
        <Provider store={Store(history)}>
            <ConnectedRouter history={history}>
                <AppContainer>
                    <Root />
                </AppContainer>
            </ConnectedRouter>
        </Provider>,
        document.getElementById('application'),
    );
};

render();

if (module.hot) {
    module.hot.accept('./Root', () => { render(); });
}

Providerコンポーネントが一番外側にあったRootコンポーネントよりいろいろ引っ越し。要はAppContainerコンポーネントの内側にReduxストアを置かないというのが解決方法なんでしょうね。

// Root.jsx
import React from 'react';
import { Switch, Route, Redirect } from 'react-router';

import { BookList, BookReader, NotFound, Navigation } from './containers';

export default () => (
    <Navigation>
        <Switch>
            <Redirect exact from="/" to="/book" />
            <Route exact path="/book"><BookList /></Route>
            <Route path="/book/:title/:vol/:page"><BookReader /></Route>
            <Route><h1>Not Found</h1></Route>
        </Switch>
    </Navigation>
);

いろんなものがなくなって、ルーティングの定義だけになった。これはこれでキレイ。

完成!

やっと期待していた動きになりました。画面コンポーネントの細々とした変更を行ってもその変更部分だけを書き換えます。ConnectedRouterはAppContainerコンポーネントの内側に置いたままでも動きに変わりありませんが、こちらもhistoryを必要とする都合からコードの書きやすさとしてApp.jsxのほうに持ってきて、どうせならばと外側に置きました。

わかってみると当然なんですが、ReduxストアまわりReducer・Middlewareを書き換えると全画面再描画が走ります。テストを書き換えてももちろんピクリとも動かないし自動バンドル時にトランスパイルエラーが出た時にもピクリとも動きません。なんかあたりまえのことが素敵、ビバ!

まとめ

react-hot-loader v3の設定をきちんとしたあと、react-reduxのProviderコンポーネントはAppContainerコンポーネントの外側に置くことがソリューションだった。react-routerだけだったらもっと早くに解決していたことでしょう。もしかするとreact-router v3でもHMRがきちんとできたのかもしれないけど、react-router v4はとても筋の良いアーキテクチャに変更されたし、react-router-reduxもまだベータ版ながらとても安定していると思うので、ここ数日のマイグレーション作業はとても有意義な結果となりました。

大事なことなのでもう一度。react-reduxのHMRが問題であって、react-routerのほうではなかった。

次は

開発環境云々のところは終わったので、アプリのコード書きながら必要に応じて技術検証しますが、どうもフォームのライブラリ、redux-formはやりすぎなんじゃないかと思い始めた。reduxアプリケーション、つまりはFLUXだと言ったって、そのコンポーネントの中で閉じたものはstateを使ったほうがいい場合だってあると思います。私はドロワーの開閉にもAction/Reducerを用意しているけどこれはやりすぎな気がする。そしてフォームもその類のものなんじゃないかなと思い始めた。

とわいえredux-formが手に馴染んでるから、お試しにMaterial-UIのフォームパーツを素につかって書いて見ながら考え直すこととしよう。とりあえずドロワーの開閉はReact Stateを用いるように書き換えた。