React + Redux + TypescriptでHMR

# 概要
React + ReduxでTypescriptを使ってHot Module Replacement (HMR)する際のメモ。
なんでこれでOKなのかは詳しく調べないと分からず。
最小構成のプロジェクトを別途作成する。
Webpack-dev-serverを使う前提


# 準備
```
npm install --save-dev babel-loader @babel/core react-hot-loader @types/webpack-env
```

| モジュール              | 導入理由                   | 補足 |
|:-----------------------|:--------------------------|:-----|
| babel-loader           | react-hot-loaderが使う     | *1   |
| @babel/core            | babel-loaderが利用         |    |
| react-hot-loader       | Reactのstateを維持できるようにする | |
| @types/webpack-env     | hot.moduleの型を使えるように | |
*1 TypeScriptだとbabel必要なかったのだが仕方なし。


# Webpackの設定
```webpack.config.js
const path = require('path');
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') .BundleAnalyzerPlugin;

module.exports = (env, argv) => {
  let config = {
    performance: { hints: false }, 
    entry      : { app:[
      // 必要性はよくわからず。
      'webpack-dev-server/client?http://localhost:8080', 
      // 必要性はよくわからず
     'webpack/hot/only-dev-server',
      './dist/index.js'
    ]},
    output: {
      path    : path.join(__dirname,'public/js'),
      filename: '[name].js',
      publicPath: '/js/'
    },
    plugins: [
      // NamedModulesPluginはなくていいみたい。
      //new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin(), 
    ],
    devServer: {
      historyApiFallback: true,
      contentBase       : path.join(__dirname,'public'),
      port              : 8080,
      hot               : true,
      publicPath        : '/js/',
      watchOptions      : {
        aggregateTimeout: 300,
        poll            : 1000
      }
    },
    resolve: {
      modules: [path.resolve(__dirname, './dist'), 'node_modules'],
      alias  : {
           //省略
      },
      extensions: [".ts", ".tsx", ".js", ".json"]
    },
    module: {
      rules: [
        {
          exclude: /node_modules/,
          test   : /\.js$/,
          //loader : ['react-hot-loader/webpack', 'babel-loader'] 
          //hot-loader/webpackはいらない様子。
          // 入れるとmonorepo全部にhot-loaderのinstallを求められた。
          loader : ['babel-loader']
        }
      ]
    }
  };

  if (argv.mode === 'production') {
    //本番環境専用の設定
    //ファイルサイズのアナライズを実行
    //config.plugins.push(new BundleAnalyzerPlugin());
  } else {
    //開発環境専用の設定
    config.cache = true;
    config.devtool = 'inline-source-map';
  }

  return config;
};

```

# Componentの設定
```index.tsx
import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(<App />, document.querySelector('#root'));
```

```App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { configureStore } from './ConfigureStore';
import Navigator from './Navigator';

const store = configureStore();
const App = () => {
  return (
    <Provider store={store}>
        <Navigator />
    </Provider>
  );
};

export default App; //Appをhot()で囲ったら動かなかった。Providerの内側にする必要があるみたい。
```

```Navigator.tsx

import React from 'react';
import { hot } from 'react-hot-loader/root';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import AuthPage from './AuthPage';
import TopPage from './TopPage';

const Navigator = () => {
  return (
    <BrowserRouter>
      <Switch>
         <Route exact={true} path='/login' component={AuthPage} />
         <Route exact={true} path='/top' component={TopPage} />
      </Switch>
    </BrowserRouter>
  );
};

export default hot(Navigator); // なのでNavigatorをhotで括った
```

# Reducerの設定
```StoreConfigure.ts
import { createStore, combineReducers, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';

// 後述する通り、RootReducerはexport defaultした方が良さそう
import rootReducer from './RootReducer';

// Sagaもif module.hotしないと変更に追従できなそう。
import rootSaga from './RootSaga';

export const configureStore = (initialState = {}) => {
  if (module.hot) {
    module.hot.accept('./RootReducer', () => {
      console.log('module.hot.accept');
      // RootReducerはexport defaultしてるので、require.default
　　　// RootReducerをexportにして、requireに変えたら動かなかった様子。
      const nextRootReducer = require('./RootReducer').default;
      store.replaceReducer(nextRootReducer);
    });
  }

  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(rootReducer, initialState, applyMiddleware(sagaMiddleware));
  sagaMiddleware.run(rootSaga);

  return store;
};
```

# GitHub
未作成