Webpack4を試すついでReact-Router4

タイトルの通りですが、Webpack4がリリースされたの試すついでにReact-Routerのv4の記事を書くつもりが全然書いてなかったのでメモ程度に。

## Webpackのインストール

今までとは違い`webpack-cli`のインストールする必要があります。

```bash
$ npm install --save-dev webpack webpack-cli
```

インストールはこれでOK。webpack4の変更点や説明は早々と色々と記事があるので割愛しますが、このあたりを参考にしました。

- [webpack v4について](https://speakerdeck.com/mukai21/webpack-v4nituite)
- [設定ファイル不要！webpack 4 でReactをビルドしてみた](https://qiita.com/shisama/items/730e7a2e6a3090a63a38)
- [最新版で学ぶwebpack 4入門 – JS開発のモジュールバンドラ](https://ics.media/entry/12140)

今回React + React-Router v4についても動かしたいので、続けて以下をインストールします。

```bash
$ npm install --save react react-dom react-router react-router-dom
$ npm install --save-dev babel-loader babel-preset-es2015 babel-preset-react
```

`react-router-dom`をインストールしていますが、これはReact-Router4から必要なものになります。色々と分離していってインストールするものが増えてますが、愛嬌ということで...

`webpack-dev-server`は以前から存在しますが、webpack4でも使えるものかと思い、インストールしておきます。

```bash
$ npm install --save-dev webpack-dev-server
```

## 設定ファイル（webpack.config.js）

設定ファイルは不要になったという記事をみますが、違いを確認するためここでは今まで通り設定ファイルを準備します。

```js:webpack.config.js
module.exports = {
  entry: {
    'app': `${__dirname}/src/javascripts/app.js`
  },
  output: {
    path: `${__dirname}/public/assets/javascripts/`,
    filename: '[name].js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['es2015', 'react']
        }
      }
    }]
  },
  devServer: {
    contentBase: `${__dirname}/public/`,
    publicPath: `/assets/javascripts/`,
    watchContentBase: true,
    open: true,
    port: 3000
  }
}
```

特に変わった様子はありません。
entryやoutputはデフォルトを使用するようであれば特に記述の必要はないようです。
ちなみにentryのデフォルトは`src/index.js`、outputのデフォルトは`dist/main.js`となるようです。
気をつける点で、webpack4から`mode`を設定する必要があり、`development`もしくは`production`を指定する必要があります。設定ファイルをそれぞれで分ける場合には、ファイル内に記述することもできますが、今回は`package.json`で確認用とビルド用でmodeをわけて確認しました。
以下をpackage.jsonのscriptsに追記します。

```json:package.json
  "scripts": {
    "build": "webpack --mode production",
    "start": "webpack-dev-server --mode development"
  },
```

これで開発（確認）するときは`npm run start`、ビルドして本番環境にデプロイする場合には`npm run build`できるようにしています。

## 構成

順番が逆になりましたが今回は以下のような構成で構築しています。

react-router-v4
┣ public
┃ ┣ assets
┃ ┃ ┗ javascripts
┃ ┗ index.html
┣ src
┃ ┗ javascripts
┃ 　 ┗ app.js
┣ package.json
┗ webpack.config.js

### index.html

特に説明は不要ですが、ベースだけ用意しておきます。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>React-Router-v4</title>
</head>
<body>
    <div id="app"></div>
    <script src="/assets/javascripts/app.js"></script>
</body>
</html>
```

### app.js

v3のときに書いたサンプルと同じものを作ってみました。
[React-Routerを試してみた](https://qiita.com/yaaah93/items/f548042bf664047451e4)

```js:app.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router, Route, Switch, Link, withRouter } from 'react-router-dom';

let users = [
  { id: 1, name: 'Suzuki Ichiro' },
  { id: 2, name: 'Tanaka Jiro' },
  { id: 3, name: 'Sato Hanako' }
];

const Wrapper = ({ children }) => (
  <div className="wrapper">
    <header>
      <h1>React Router v4</h1>
    </header>
    <main>
      { children }
    </main>
  </div>
);

const Index = () => (
  <div id="contents">
    <h2>Index</h2>
    <div className="action">
      <Link to="/users/create">Create</Link>
    </div>
    <ul>
    { users.map(user =>
      <li key={ user.id }>
        <Link to={ `/users/${user.id}` }>{ user.name }</Link>
      </li>
    ) }
    </ul>
  </div>
);

const Create = () => (
  <div id="contents">
    <h2>Create</h2>
    <FormWithRouter />
  </div>
);

const Edit = ({ match }) => (
  <div id="contents">
    <h2>Edit</h2>
    <FormWithRouter user={ users.filter(user => user.id == match.params.id)[0] } />
  </div>
);

class Form extends Component {
  constructor(props) {
    super(props);
    let { user } = this.props;
    this.state = { name: user ? user.name : '' };
  }
  handleKeyDown(e) {
    this.setState({ name: e.target.value });
  }
  handleSubmit(e) {
    e.preventDefault();
    const { user, history } = this.props;
    const { name } = this.state;
    if (!user) {
      const id = Math.max(...users.map(user => user.id));
      users.push({ id: id + 1, name: name });
    } else {
      users = users.map(data => {
        if (data.id == user.id) {
          return { id: user.id, name: name };
        }
        return data;
      });
    }
    history.push('/');
  }
  render() {
    const { name } = this.state;
    return (
      <form onSubmit={ this.handleSubmit.bind(this) }>
        <input type="text" name="name" value={ name } onChange={ this.handleKeyDown.bind(this) } />
      </form>
    );
  }
}
const FormWithRouter = withRouter(Form);

render((
  <Router>
    <Wrapper>
      <Switch>
        <Route exact path="/" component={ Index } />
        <Route path="/users/create" component={ Create } />
        <Route path="/users/:id" component={ Edit } />
      </Switch>
    </Wrapper>
  </Router>
), document.querySelector('#app'));
```

各コンポーネントの書き方が変わってますが、実装している内容はほぼ同じことです。
React Routerの変更点でパッと見`react-router-dom`に変わってる点はありますが、色々と変わってます。
v3ではレイアウトを作るときは、Routeにコンポーネントをしていましたが、`Switch`を使うような作りに変わっています。Switchに子にはRouteだけを使ったシンプルな記述ができるようになっています。
何気にわかりづらかったのが、Linkを使わず画面遷移したい場合。
v3では`browserHistory.push('/')`みたいにわりと簡単に記述ができましたが、そもそもbrowserHistoryの書き方も変わっています。
v4では`withRouter`というものを使うことで同じことが実装できます。このサンプルではFormコンポーネント内で入力後に画面遷移を実装していますので、わかりづらさが増してるようにも見えますが、Formコンポーネントを`withRouter`でラッピングしてやるかんじになります。
SFCの場合はもっとシンプルに書けます。

```js
const Button = withRouter(({ history }) => (
  <button type="button" onClick={() => { history.push('/') }}>Back</button>
));
```

## 所感

Webpack4はプラグインをガリガリ使ってないようなシンプルなものであれば、2から3にバージョンアップしたときのように苦労？することはあまりないのかなと思います。ビルド速度も体感的に早くかんじるので、いいかんじです。webpack-dev-serverも問題なく使えたし。

React-Routerはv4がリリースされて1年ほど経過してるのでありふれてる情報になってしまいましたが、書くといって書いてなかったので...メモ程度です。
今回は書いてませんがNavLinkもナビゲーションメニューを作る時にはわりと使えると思います。