タイトルの通りですが、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)
設定ファイルは不要になったという記事をみますが、違いを確認するためここでは今まで通り設定ファイルを準備します。
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に追記します。
"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
特に説明は不要ですが、ベースだけ用意しておきます。
<!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を試してみた
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もナビゲーションメニューを作る時にはわりと使えると思います。