LoginSignup
14
15

More than 5 years have passed since last update.

Webpack4を試すついでReact-Router4

Posted at

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

Webpackのインストール

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

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

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

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

$ 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でも使えるものかと思い、インストールしておきます。

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

設定ファイル(webpack.config.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に追記します。

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

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

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を試してみた

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の場合はもっとシンプルに書けます。

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

所感

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

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

14
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
15