今から始めるReact入門 〜 React Router

# 目次
- [今から始めるReact入門 〜 React の基本](https://qiita.com/TsutomuNakamura/items/72d8cf9f07a5a30be048 "今から始めるReact入門 〜 React の基本")
- [今から始めるReact入門 〜 React Router 編](https://qiita.com/TsutomuNakamura/items/34a7339a05bb5fd697f2 "今から始めるReact入門 〜 React Router 編")  ←★ここ
- [今から始めるReact入門 〜 flux編](https://qiita.com/TsutomuNakamura/items/cb3b6109fb21730cd73f "今から始めるReact入門 〜 flux編")
- [今から始めるReact入門 〜 Redux 編: immutability とは](https://qiita.com/TsutomuNakamura/items/a78514a61ede444bd074 "今から始めるReact入門 〜 Redux 編: immutability とは")
- [今から始めるReact入門 〜 Redux 編: Redux 単体で状態管理をしっかり理解する](https://qiita.com/TsutomuNakamura/items/2ded5112ca5ded70e573 "今から始めるReact入門 〜 Redux 編: Redux 単体で状態管理をしっかり理解する")
- [今から始めるReact入門 〜 Redux 編: Redux アプリケーションを作成する](https://qiita.com/TsutomuNakamura/items/ceaa2552bbbca1bac24e "今から始めるReact入門 〜 Redux 編: Redux アプリケーションを作成する")
- [今から始めるReact入門 〜 Mobx 編](https://qiita.com/TsutomuNakamura/items/f10491060f0f1640afd9 "今から始めるReact入門 〜 Mobx 編")

# Single Page Application (SPA)について 
今回はreact-router を使用して宣言的記法を利用したSingle Page Application (SPA) の作成について勉強していきたいと思います。
React Router はUI とURL を同期させるライブラリで、例えば閲覧者がReact アプリケーションにアクセスした時に`http://app.example.com/` とアクセスした時はHome のコンポーネントをレンダリングし、`http://app.example.com/address` とアクセスした時は住所を表示するコンポーネントをレンダリングしたりといった操作を簡単にできるようになります。

# 本記事で使用するファイル 
本記事で使用するファイルは以下のリポジトリにあります。

- https://github.com/TsutomuNakamura/my-react-js-tutorials/tree/master/2-react-router/00_start_point/react_router

# プロジェクトの作成 
前回のプロジェクトとは別に完全に新しいプロジェクトを作成します。
今回のアプリはBootstrap のテーマを利用していきますが、Bootstrap は一般的にモーションを作成するのにjQuery を必要としていますが、ちょっとしたサンプル程度で今回はjQuery によるモーションは不要なためjQuery はロードしません。

```console:command
$ mkdir react_router
$ cd react_router
$ npm init -y
$ npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader \
        webpack webpack-cli webpack-dev-server \
        react react-dom \
        react-router react-router-dom
```

package.json のscripts にstart コマンドでwebpack-dev-server が起動するように設定します。

```javascript:package.json
  "scripts": {
    "start": "webpack-dev-server --content-base src --mode development --inline",    // <- 追加
    ......
  },
```

今回は、事前に静的なWeb ページとして、ある程度完成しているものをReact (JavaScript) 側へ移行して動的にレンダリングするように修正していきたいと思います。
これより以下のファイルは[GitHub リポジトリ](https://github.com/TsutomuNakamura/my-react-js-tutorials/tree/master/2-react-router/00_start_point/react_router "GitHub リポジトリ")を参考に準備してください。以下の状態からアプリケーション開発を進めていきます。

```javascript:webpack.config.js
var debug   = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path    = require('path');

module.exports = {
  context: path.join(__dirname, "src"),
  entry: "./js/client.js",
  module: {
    rules: [{
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      use: [{
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-react', '@babel/preset-env']
        }
      }]
    }]
  },
  output: {
    path: __dirname + "/src/",
    filename: "client.min.js",
    publicPath: '/'
  },
  devServer: {
    historyApiFallback: true
  },
  plugins: debug ? [] : [
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
  ],
};
```

```javascript:src/js/pages/Layout.js
import React from "react";

export default class Layout extends React.Component {
  render() {
    return (
      <h1>KillerNews.net</h1>
    );
  }
}
```

```html:src/index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="">
  <meta name="author" content="">
  <title>React</title>
  <!-- Bootstrap Core CSS -->
  <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/cerulean/bootstrap.min.css" rel="stylesheet">

  <!-- Custom Fonts -->
  <!-- <link href="font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> -->
  <link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
</head>

<body>

  <!-- Navigation -->
  <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container">
      <!-- Brand and toggle get grouped for better mobile display -->
      <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
      </div>
      <!-- Collect the nav links, forms, and other content for toggling -->
      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
        <ul class="nav navbar-nav">
          <li>
            <a href="#">Featured</a>
          </li>
          <li>
            <a href="#">Archives</a>
          </li>
          <li>
            <a href="#">Settings</a>
          </li>
        </ul>
      </div>
      <!-- /.navbar-collapse -->
    </div>
  </nav>
  <!-- Page Content -->
  <div class="container" style="margin-top: 60px;">
    <div class="row">
      <div class="col-lg-12">
        <div id="app"></div>
      </div>
    </div>
    <!-- Call to Action Well -->
    <div class="row">
      <div class="col-lg-12">
        <div class="well text-center">
          Ad spot goes here
        </div>
      </div>
      <!-- /.col-lg-12 -->
    </div>
    <!-- /.row -->
    <!-- Content Row -->
    <div class="row">
      <div class="col-md-4">
        <h2>Heading 1</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe rem nisi accusamus error velit animi non ipsa placeat. Recusandae, suscipit, soluta quibusdam accusamus a veniam quaerat eveniet eligendi dolor consectetur.</p>
        <a class="btn btn-default" href="#">More Info</a>
      </div>
      <!-- /.col-md-4 -->
      <div class="col-md-4">
        <h2>Heading 2</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe rem nisi accusamus error velit animi non ipsa placeat. Recusandae, suscipit, soluta quibusdam accusamus a veniam quaerat eveniet eligendi dolor consectetur.</p>
        <a class="btn btn-default" href="#">More Info</a>
      </div>
      <!-- /.col-md-4 -->
      <div class="col-md-4">
        <h2>Heading 3</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe rem nisi accusamus error velit animi non ipsa placeat. Recusandae, suscipit, soluta quibusdam accusamus a veniam quaerat eveniet eligendi dolor consectetur.</p>
        <a class="btn btn-default" href="#">More Info</a>
      </div>
      <!-- /.col-md-4 -->
    </div>
    <!-- /.row -->
    <!-- Footer -->
    <footer>
      <div class="row">
        <div class="col-lg-12">
          <p>Copyright &copy; KillerNews.net</p>
        </div>
      </div>
    </footer>
  </div>

  <!-- /.container -->
  <script src="client.min.js"></script>
</body>
</html>
```

```javascript:src/js/client.js
import React from "react";
import ReactDOM from "react-dom";

import Layout from "./pages/Layout";

const app = document.getElementById('app');

ReactDOM.render(<Layout />, app);
```

ここまで準備できたら、一旦Web サーバを起動してページを確認してみましょう。

```console:console
$ npm start
```

サーバが起動後、`http://localhost:8080` へWeb ブラウザでアクセスします。
![React_ReactRouter0001.png](https://qiita-image-store.s3.amazonaws.com/0/70152/167253fb-4363-8c38-28b0-938804829a0f.png)

このページをReact router を使ってSPA として動的に画面描写を変えていくようにしたいと思います。

# React Router の導入 
React の環境が完成したら次はreact-router 関連のパッケージをインストールします。

```console:console
$ npm install --save-dev react-router react-router-dom
$ # react-router v4 からはreact-router-dom も必要になります
```

必要なライブラリをインストールしたら、各コンポーネントを作成していきます。
まずは記事をレンダリングするFeatured コンポーネントを作成します。

```javascript:src/js/pages/Featured.js
import React from "react";

export default class Featured extends React.Component {
  render() {
    return (
      <h1>Featured</h1>
    );
  }
}
```

```javascript:src/js/pages/Archives.js
import React from "react";

export default class Archives extends React.Component {
  render() {
    return (
      <h1>Archives</h1>
    );
  }
}
```

```javascript:src/js/pages/Settings.js(new)
import React from "react";

export default class Settings extends React.Component {
  render() {
    return (
      <h1>Settings</h1>
    );
  }
}
```

各コンポーネントの下地は作成完了しました。次はclient.js ファイルを編集して、React Router を使っていきます。

```diff_javascript:src/js/client.js
 import React from "react";
 import ReactDOM from "react-dom";
+import { BrowserRouter as Router, Route } from "react-router-dom";
 
 import Layout from "./pages/Layout";
+import Featured from "./pages/Featured";
+import Archives from "./pages/Archives";
+import Settings from "./pages/Settings";
 
 const app = document.getElementById('app');
 
-ReactDOM.render(<Layout />, app);
+ReactDOM.render(
+  <Router>
+    <Layout>
+      <Route exact path="/" component={Featured}></Route>
+      <Route path="/archives" component={Archives}></Route>
+      <Route path="/settings" component={Settings}></Route>
+    </Layout>
+  </Router>,
+app);
```

上記のようにコンポーネントを列挙するとReact Router の特徴がわかりやすいと思いますが、React Router ではユーザがそれぞれ`/`, `/archives`, `/settings` のパスにアクセスした時に表示されるコンポーネントをそれぞれ、`Featured`, `Archives`, `Settings` になるように上記のように指定できます。
また、上記のReact Router の`<Route exact path="/" component={Featured}></Route>` のみ`exact` というキーワードがついている点にも注目してください。
これはユーザがアクセスしたパスが`/` と厳密にマッチしたときのみ表示されるコンポーネントであることを意味します。
`http://app.example.com/` というURL にアクセスした時に表示されるコンポーネントになります。
この`exact` というキーワードが無いと`/foo` にも`/bar` にも`/archives` にもFeatured コンポーネントが表示されることになるので注意してください。
要するにユーザが`/archives` というパスにアクセスすると`Featured` コンポーネントも`Archives` コンポーネントも表示されるという事態が発生します。
またこの`exact`を使ったコンポーネントの指定はreact-router v3 までのIndexRoute に相当しますので、react-router v4 からはexact を使うようにしてください。

また他の`exact`がない`<Route path="/archives" component={Archives}></Route>` はユーザが`/archives/foo`, `/archives/bar` とアクセスした場合にも表示されるコンポーネントとなります。

…という感じでReact Router のおおまかな特徴がわかったところで、もう一度`npm start` して`http://localhost:8080` を確認してみましょう。
一番最初の時と同じレイアウトで、エラー無く表示されればOK です。とりあえずここまでできればReact Router の次のステップへの準備は完了です。

## Link の追加

Layout.js を修正しArchive, Settingsへのリンクを追加してみましょう。

```diff_javascript:src/js/pages/Layout.js
 import React from "react";
+import { Link } from "react-router-dom";
 
 export default class Layout extends React.Component {
   render() {
     return (
+      <div>
         <h1>KillerNews.net</h1>
+        {this.props.children}
+        <Link to="/archives">archives</Link>,
+        <Link to="/settings">settings</Link>
+      </div>
     );
   }
 }
```

上記の記述をすることで、各リンクをクリックするとclient.js に埋め込まれたRoute コンポーネントがLayout コンポーネントへ渡されるようになり、Layout コンポーネントの`this.props.children` からRoute コンポーネントを参照することができるようになります。

```javascript:src/js/client.js

ReactDOM.render(
  <Router>
    <Layout>                                                 // (2) Layout に渡り、Layout からthis.props.children で参照できる
      <Route exact path="/" component={Featured}></Route>
      <Route path="/archives" component={Archives}></Route>  // (1) 例えばLayout のarchives をクリックするとArchives が...
      <Route path="/settings" component={Settings}></Route>
    </Layout>
  </Router>,
app);
```

ここで再度`http://localhost:8080` を表示してarchives とsettings のリンクをそれぞれクリックしてみましょう。
各Archives コンポーネントとSettings コンポーネントが交互に切り替わって表示されるでしょう。

![React_ReactRouter0002.gif](https://qiita-image-store.s3.amazonaws.com/0/70152/98e1a452-5a7e-0250-635f-4e303c5338f3.gif)

## Link をbutton で装飾してみる 

```diff_javascript:src/js/pages/Layout.js
 import React from "react";
 import { Link } from "react-router-dom";
 
 export default class Layout extends React.Component {
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
-        <Link to="/archives">archives</Link>
-        <Link to="/settings">settings</Link>
+        <Link to="/archives"><button class="btn btn-danger">archives</button></Link>
+        <Link to="/settings"><button class="btn btn-success">settings</button></Link>
       </div>
     );
   }
 }
```

button 要素に対して`class="btn btn-danger"` とすることで、BootStrap のCSS が適用され、装飾されたボタンが表示されるようになります。
が、実のところ`class` というキーワードはJavaScript の予約語なので、JavaScript のソースコード内に、このように利用することができません。
実際、JSX では`class` では無く、`className` を使ってHTML のclass 属性を表すようになっています。

しかし、class キーワードがJavaScript の予約後だからといってJSX 内で使えないようでは、JavaScript の中にわざわざHTML タグライクなJSX を取り入れた利点が薄くなってしまうでしょう…。
そう思う場合は`babel-plugin-react-html-attrs` を取り入れて、JSX 内でも`class` キーワードを使えるようにしてしまいましょう。

```console:babel-plugin-react-html-attrsインストール
$ npm install --save-dev babel-plugin-react-html-attrs
```

```diff_javascript:webpack.config.js
 var debug   = process.env.NODE_ENV !== "production";
 var webpack = require('webpack');
 var path    = require('path');
 
 module.exports = {
   context: path.join(__dirname, "src"),
   entry: "./js/client.js",
   module: {
     rules: [{
       test: /\.jsx?$/,
       exclude: /(node_modules|bower_components)/,
       use: [{
         loader: 'babel-loader',
         options: {
+          plugins: ['react-html-attrs'],
           presets: ['@babel/preset-react', '@babel/preset-env']
         }
       }]
     }]
   },
   output: {
     path: __dirname + "/src/",
     filename: "client.min.js"
   },
   plugins: debug ? [] : [
     new webpack.optimize.OccurrenceOrderPlugin(),
     new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
   ],
 };
```

ここで、もう一度`npm start` コマンドを実行し直してください。
その後、Web ブラウザで`http://localhost:8080` をもう一度確認してみてください。

![React_ReactRouter0003.png](https://qiita-image-store.s3.amazonaws.com/0/70152/a7e73332-294f-86ec-56e7-141a8fbb6787.png)

ボタンがBootstrap のCSS で装飾されていることが確認できましたでしょうか？
このようにJSX はHTML と同じように`class`(babel-plugin-react-html-attrs が無い場合はclassName) 属性を使用してHTML のclass 属性を指定することができるようになります。

## 補足: その他のclass 属性の指定方法 
上記のソースコードではbutton 要素を作成して、そこにclass 属性を追加しましたが、react-router-dom のLink コンポーネントにclass 属性を追加しても同様なことができます。

```diff_javascript:src/js/pages/Layout.js
 import React from "react";
 import { Link } from "react-router-dom";
 
 export default class Layout extends React.Component {
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
-        <Link to="/archives">archives</Link>
-        <Link to="/settings">settings</Link>
+        <Link to="/archives" class="btn btn-danger">archives</Link>
+        <Link to="/settings" class="btn btn-success">settings</Link>
       </div>
     );
   }
 }
```

Web ブラウザで`http://localhost:8080` を確認すると先ほどと変わらずに装飾されたボタンが表示されます。

## navigate 
React Router では普通に使っている状態で、各ボタンを押下してPath が変わった時に、表示されていた状態がブラウザの履歴にも蓄積されるようになります。
そのため、何かボタンを押下してからブラウザの戻るボタンを押下すると一つ前の状態に戻ることが確認できます。
今回構築しているアプリはシングルページアプリケーション(SPA) で画面遷移をしていないので、戻るボタンを押下した場合はアプリケーションを開く前のページ(http://localhost:8080 を表示する前に表示されていたサイト) に遷移することになるかと思われがちですが、そうはなりません。

navigate を使ってこの履歴の管理をもう少し柔軟にカスタマイズしてみましょう。
ただし、react-router v4 からnavigate を使って履歴を管理するにはwithRouter を使用することになります。
そのため、複雑になることを避けるために特に理由がない限りはデフォルトのままを利用したほうが良いでしょう。

- this.props.history の関数一覧
    - `this.props.history.push` 画面遷移する。前居た画面を履歴に追加し、ブラウザの戻るボタンで戻れるようにする(React Router で標準的な挙動)。
    - `this.props.history.replace` 画面遷移する。前居た画面を置換するため、ブラウザの戻るボタンで戻れない。

例えば、`this.props.history.push` を使用する場合は次のようになります。

```diff_javascript:src/js/pages/Layout.js
 import React from "react";
-import { Link } from "react-router-dom";
+import { Link, withRouter } from "react-router-dom";
 
-export default class Layout extends React.Component {
+class Layout extends React.Component {
+  navigate() {
+    console.log(this.props.history);
+    this.props.history.push("/");
+  }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives" class="btn btn-danger">archives</Link>
         <Link to="/settings" class="btn btn-success">settings</Link>
+        <button class="btn btn-info" onClick={this.navigate.bind(this)}>featured</button>
       </div>
     );
   }
 }
-
+export default withRouter(Layout);
```

![React_ReactRouter0004.gif](https://qiita-image-store.s3.amazonaws.com/0/70152/14192355-53ad-e6a7-7e59-aea7caa9d8d9.gif)

Web 画面を確認すると…

Archives クリック -> Featured クリック(push) -> Settings クリック -> 戻るボタン押下(Featured へ) -> 戻るボタン押下(Archives へ(pop)) -> 戻るボタン押下(初期画面へ) という流れになっており、history へのpush とpop が確認できました。
次は`this.props.history.replace()` を使って履歴に残らない遷移を実装してみましょう。

```diff_javascript:src/js/pages/Layout.js
 import React from "react";
 import { Link, withRouter } from "react-router-dom";
 
class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
-    this.props.history.push("/");
+    this.props.history.replace("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives" class="btn btn-danger">archives</Link>
         <Link to="/settings" class="btn btn-success">settings</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>featured</button>
       </div>
     );
   }
 }
 
export default withRouter(Layout);
```

![React_ReactRouter0004_02.gif](https://qiita-image-store.s3.amazonaws.com/0/70152/78a2ceae-9a45-a840-3009-2768b6965af3.gif)

上記のようにすると、featured ボタンを押下した時に表示されていた画面(settings) は履歴に蓄積されていないことがわかると思います。featured 画面の履歴がなくなるというわけではない点に注意してください。
このようにボタンをクリックした時に、ブラウザの履歴に残したくない場合は`this.props.history.replace("/");` を使用するようにしましょう。

なお、ここで実施したステータスの変更は、次のレッスンに進むために元に戻しておくようにしてください。


```diff_javascript:src/js/pages/Layout.js
 import React from "react";
-import { Link } from "react-router-dom";
+import { Link, withRouter } from "react-router-dom";
 
-export default class Layout extends React.Component {
+class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
-    this.props.history.replace("/");
+    this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives" class="btn btn-danger">archives</Link>
         <Link to="/settings" class="btn btn-success">settings</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>featured</button>
       </div>
     );
   }
 }
 
+export default withRouter(Layout);
```

## URL のパラメータ取得 

ここではURL に情報を渡す方法として手っ取り早い方式であるGet パラメータを使ってアクセスされた時にURL パラメータから情報を取得する方法について勉強していきます。

例えば、http://localhost:8080/archives/some-article とURL が入力された時に"some-article" の部分を取得したい場合、`:変数名` という形式で取得することができます。
また`path="/archives"` と`path="/archives/:article"` が部分一致するため、`/archives/foo` ロケーションにアクセスした時に両方のコンポーネントがレンダリングされることになります。
そのため、短い方の`path="/archives"` に`exact` キーワードを追加して完全一致した場合のみレンダリングするように変更します。


```diff_javascript:src/js/pages/client.js
 import React from "react";
 import ReactDOM from "react-dom";
 import { BrowserRouter as Router, Route } from "react-router-dom";
 
 import Layout from "./pages/Layout";
 import Featured from "./pages/Featured";
 import Archives from "./pages/Archives";
 import Settings from "./pages/Settings";
 
 const app = document.getElementById('app');
 
 ReactDOM.render(
   <Router>
     <Layout>
       <Route exact path="/" component={Featured}></Route>
-      <Route path="/archives" component={Archives}></Route>
+      <Route exact path="/archives" component={Archives}></Route>
+      <Route path="/archives/:article" component={Archives}></Route>
       <Route path="/settings" component={Settings}></Route>
     </Layout>
   </Router>,
 app);
```

このGet パラメータの値は`<Route path="/archives/:article" component={Archives}></Route>` とすることでArchives コンポーネントへ`/archives`以下の値が渡るようになります。
上記の例では、具体的にArchives コンポーネント内で`this.props.match.params.article` とすることでパラメータを取得することができます(もしどんなパラメータ名でArchives コンポーネントにパラメータが渡っているかわかりにくい場合は、Archives コンポーネント内で`console.log(this.props);` とすることで、渡っているパラメータ一覧が見れるので確認してみると良いでしょう)。


```diff_javascript:src/js/pages/Archives.js
 import React from "react";
 
 export default class Archives extends React.Component {
   render() {
     return (
-      <h1>Archives</h1>
+      <h1>Archives ({this.props.match.params.article})</h1>
     );
   }
 }
```

Layout.js にリンクを追加

```diff_javascript:src/js/pages/Layout.js
 import React from "react";
 import { Link, withRouter } from "react-router-dom";

 class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
     this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
+        <Link to="/archives/some-other-articles" class="btn btn-warning">archives (some other articles)</Link>
         <Link to="/archives" class="btn btn-danger">archives</Link>
         <Link to="/settings" class="btn btn-success">settings</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>feathred</button>
       </div>
     );
   }
 }
 export default withRouter(Layout);
```


`http://localhost:8080` をWeb ブラウザで表示して、`archives (some other articles)` と`archives` リンクをクリックしてみましょう。
前者をクリックすると`http://localhost:8080/archives/some-other-articles` へ飛び、パラメータである`(some-other-articles)` が画面に表示サれるようになり、後者をクリックすると`http://localhost:8080/archives` へ飛び、空パラメータである`()` が表示されるようになっています。

![React_ReactRouter0005.gif](https://qiita-image-store.s3.amazonaws.com/0/70152/d56c4ed1-425b-ae9d-9c31-c5959a105187.gif)

## URL のパラメータを正規表現で指定する 

子コンポーネントに渡すパラメータ名ですが、正規表現で指定することができます。
例えば下記の例はSettings コンポーネントに`this.props.match.params.mode` という変数名で"/settings/main" もしくは"/settings/extra" というパス値のときのみ動作するコンポーネントを定義することができます。

```diff_javascript:src/js/pages/client.js
 import React from "react";
 import ReactDOM from "react-dom";
 import { BrowserRouter as Router, Route } from "react-router-dom";
 
 import Layout from "./pages/Layout";
 import Featured from "./pages/Featured";
 import Archives from "./pages/Archives";
 import Settings from "./pages/Settings";
 
 const app = document.getElementById('app');
 
 ReactDOM.render(
   <Router>
     <Layout>
       <Route exact path="/" component={Featured}></Route>
       <Route path="/archives/:article" component={Archives}></Route>
-      <Route path="/settings" component={Settings}></Route>
+      <Route path="/settings/:mode(main|extra)" component={Settings}></Route>
     </Layout>
   </Router>,
 app);
```

上記のように定義すると、Settings コンポーネントで`this.props.match.params.mode` で値を参照できるようになります。

```diff_javascript:src/js/pages/Settings.js
 import React from "react";
 
 export default class Settings extends React.Component {
   render() {
+    const type = (this.props.match.params.mode == "extra"? " (for experts)": "");
     return (
-      <h1>Settings</h1>
+      <h1>Settings{type}</h1>
     );
   }
 }
```

`/settings/main`, `/settings/extra` へのボタンを追加します。

```diff_javascript:src/js/pages/Layout.js
 import React from "react";
 import { Link, withRouter } from "react-router-dom";
 
 class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
     this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives/some-other-articles" class="btn btn-warning">archives (some other articles)</Link>
         <Link to="/archives" class="btn btn-danger">archives</Link>
-        <Link to="/settings" class="btn btn-success">settings</Link>
+        <Link to="/settings/main" class="btn btn-success">settings</Link>
+        <Link to="/settings/extra" class="btn btn-success">settings (extra)</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>feathred</button>
       </div>
     );
   }
 }
 export default withRouter(Layout);
```

ページを開いて`settings`, `settings (extra)` ボタンをクリックしてみましょう。

![React_ReactRouter0007.gif](https://qiita-image-store.s3.amazonaws.com/0/70152/615942cc-7b35-27ab-f71a-a8148e4530e9.gif)

上記のように`this.props.match.param.mode` の値、main, extra を受け取れていることがわかります。
またここではclient.js にて`<Route path="/settings/:mode(main|extra)" component={Settings}></Route>` という書き方をしていますが、もしmain でもなくextra でもない値が渡された場合はSettings コンポーネントのレンダリングは行われません。

## クエリストリングを取得する 

React Router でクエリストリングを扱う方法について紹介します。
クエリストリングとはブラウザなどでURL を`http://localhost:8080/archives?date=today&filter=hot` と入力した時に、パラメータとして渡す`?date=today&filter=hot` のkey=value の値のことになります。

しかし、このquery stringをReact Router で解析するやり方ですが、React Router v4 で撤去されてしまったようです。

- https://github.com/ReactTraining/react-router/issues/4410

なのでReact Router v4 からは独自にクエリストリングを解析する必要がありますが、ここではURLSearchParams を使った方法を紹介します。
まずはトップ画面にクエリストリングを含んだリンクボタンを追加します。


```diff_javascript:src/js/pages/Layout.js
 import React from "react";
 import { Link, withRouter } from "react-router-dom";
 
 class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
     this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
-        <Link to="/archives/some-other-articles" class="btn btn-warning">archives (some other articles)</Link>
-        <Link to="/archives" class="btn btn-danger">archives</Link>
+        <Link to="/archives/some-other-articles?date=yesterday&filter=none" class="btn btn-warning">archives (some other articles)</Link>
+        <Link to="/archives?date=today&filter=hot" class="btn btn-danger">archives</Link>
         <Link to="/settings/main" class="btn btn-success">settings</Link>
         <Link to="/settings/extra" class="btn btn-success">settings (extra)</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>feathred</button>
       </div>
     );
   }
 }
 export default withRouter(Layout);
```


次にArchives コンポーネントにクエリストリングを解析して、その結果を画面に表示するようにします。


```diff_javascript:src/js/pages/Archives.js
 import React from "react";
 
 export default class Archives extends React.Component {
   render() {
+    const query = new URLSearchParams(this.props.location.search)
+    let message
+        = (this.props.match.params.article ? this.props.match.params.article + ", ": "")
+        + "date=" + query.get("date") + ", filter=" + query.get("filter");
     return (
-      <h1>Archives ({this.props.match.params.article})</h1>
+      <h1>Archives ({message})</h1>
     );
   }
 }
```

![React_ReactRouter0009.gif](https://qiita-image-store.s3.amazonaws.com/0/70152/ffdc4d02-ac6b-3099-16ec-353a5064ff49.gif)

React Router v4 になってもURLSearchParams を使えばキレイにクエリパラメータを取得することができました。

## 補足: URLSearchParams をサポートしていないブラウザの場合 
URLSearchParams をサポートしていないブラウザの場合、`query-string` ライブラリを使って解析する方法もありです。
詳細な説明は割愛します。

## ボタンがアクティブの時のclass 指定(activeClassName) 
NavLink を使うとactiveClassName というprop を使って該当するリンクが表示されている時に適用するHTML のclass 値を指定することができます。

```diff_javascript:src/js/pages/Layout.js
 import React from "react";
-import { Link, withRouter } from "react-router-dom";
+import { NavLink, Link, withRouter } from "react-router-dom";
 
 class Layout extends React.Component {
   navigate() {
     console.log(this.props.history);
     this.props.history.push("/");
   }
   render() {
     return (
       <div>
         <h1>KillerNews.net</h1>
         {this.props.children}
         <Link to="/archives/some-other-articles?date=yesterday&filter=none" class="btn btn-warning">archives (some other articles)</Link>
         <Link to="/archives?date=today&filter=hot" class="btn btn-danger">archives</Link>
-        <Link to="/settings/main" class="btn btn-success">settings</Link>
+        <NavLink to="/settings/main" class="btn btn-success" activeClassName="btn-danger">settings</NavLink>
         <Link to="/settings/extra" class="btn btn-success">settings (extra)</Link>
         <button class="btn btn-info" onClick={this.navigate.bind(this)}>feathred</button>
       </div>
     );
   }
 }
 export default withRouter(Layout);
```

![React_ReactRouter0011.gif](https://qiita-image-store.s3.amazonaws.com/0/70152/9465c88f-ae39-c39c-d69b-ffb2668e687a.gif)

以上、React Router の基本的な機能についてはおおかた、解説はしてきました。
次は静的なHTML で記載されているページをReact のJSX に埋め込んでいき、アプリケーションを完成させていきます。

ここまでのソースコードは以下のリポジトリに格納しておきましたので、参考になればと思います。

- https://github.com/TsutomuNakamura/my-react-js-tutorials/tree/master/2-react-router/01_active_class_name

## 補足: query string が含まれる場合のactiveClassName の挙動について 
NavLink のto にquery string が含まれる場合、activeClassName に指定したclass が正しく反映されません。
これはバグではなくReact Router v4 からquery string に対しての機能を取り除いたためです。

- https://github.com/ReactTraining/react-router/issues/5379

## html 静的コンテンツのコンポーネント化 
`src/index.html` 内の静的コンテンツを各React コンポーネントに移動していきます。
今回は手順が膨大なので作成済みのコンテンツを元に、注目ポイントのみ見ていくようにします。

- https://github.com/TsutomuNakamura/my-react-js-tutorials/tree/master/2-react-router/02_migrate_html_to_react/react_router

静的コンテンツ移行後の`src/index.html` の内容は以下のようにスッキリしたものになります。
以下の`<div id="app"></div>` 内にコンポーネント化したコンテンツでデータを描写していきます。

```html:src/index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="">
  <meta name="author" content="">
  <title>React</title>
  <!-- Bootstrap Core CSS -->
  <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/cerulean/bootstrap.min.css" rel="stylesheet">

  <!-- Custom Fonts -->
  <!-- <link href="font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"> -->
  <link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
</head>

<body>
  <div id="app"></div>
  <script src="/client.min.js"></script>
</body>

</html>
```

記事のテンプレートは`src/js/components/Article.js` で、それを`src/js/pages/Featured.js` からタイトルを設定して各記事を表示させるようにします。

```js:src/js/components/Article.js
import React from "react";

export default class Article extends React.Component {
  render() {
    const { title } = this.props;

    return (
      <div class="col-md-4">
        <h4>{title}</h4>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe rem nisi accusamus error velit animi non ipsa placeat. Recusandae, suscipit, soluta quibusdam accusamus a veniam quaerat eveniet eligendi dolor consectetur.</p>
        <a class="btn btn-default" href="#">More Info</a>
      </div>
    );
  }
}
```

```js:src/js/pages/Featured.js
import React from "react";
import Article from "../components/Article";

export default class Featured extends React.Component {
  render() {
    const Articles = [
      "Some Article",
      "Some Other Article",
      "Yet Another Article",
      "Still More",
      "Some Article",
      "Some Other Article",
      "Yet Another Article",
      "Still More",
      "Some Article",
      "Some Other Article",
      "Yet Another Article",
      "Still More"
    ].map((title, i) => <Article key={i} title={title} />);

    const adText = [
      "Ad spot #1",
      "Ad spot #2",
      "Ad spot #3",
      "Ad spot #4",
      "Ad spot #5"
    ];

    const randomAd = adText[Math.round(Math.random() * (adText.length - 1))];
    console.log("featured");

    return (
      <div>
        <div class="row">
          <div class="col-lg-12">
            <div class="well text-center">
              {randomAd}
            </div>
          </div>
        </div>
        <div class="row">{Articles}</div>
      </div>
    );
  }
}
```

また画面上部のメニューに関しては`src/js/components/layout/Nav.js` で現在表示されているlocation を取得して、該当するメニューボタンのみactive なスタイルを設定するように設定します。

```javascript:src/js/components/layout/Nav.js
import React from "react";
import { Link } from "react-router-dom";

export default class Nav extends React.Component {
  constructor() {
    super();
    this.state = {
      collapsed: true
    };
  }
  toggleCollapse() {
    const collapsed = !this.state.collapsed;
    this.setState({collapsed});
  }
  render() {
    const { location } = this.props;
    const { collapsed } = this.state;
    const featuredClass = location.pathname === "/" ? "active" : "";
    const archivesClass = location.pathname.match(/^\/archives/) ? "active" : "";
    const settingsClass = location.pathname.match(/^\/settings/) ? "active" : "";
    const navClass = collapsed ? "collapse" : "";
    return (
      <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
        <div class="container">
          <div class="navbar-header">
            <button type="button" class="navbar-toggle" onClick={this.toggleCollapse.bind(this)}>
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>
          </div>
          <div class={"navbar-collapse " + navClass} id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
              <li class={featuredClass}>
                <Link to="/" onClick={this.toggleCollapse.bind(this)}>Featured</Link>
              </li>
              <li class={archivesClass}>
                <Link to="/archives" onClick={this.toggleCollapse.bind(this)}>Archives</Link>
              </li>
              <li class={settingsClass}>
                <Link to="/settings" onClick={this.toggleCollapse.bind(this)}>Settings</Link>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    );
  }
}
```

![React_ReactRouter0012.gif](https://qiita-image-store.s3.amazonaws.com/0/70152/1fcea6a4-b735-8eb9-c47b-bccd0ef0ddcc.gif)


# React Router v3 からの主な変更点一覧 
最後に、今までReact Router v3 を利用していて最近のReact Router v4 を利用し始めたときに出くわした様々な変更点は下記URL の記事にまとめておきました。React Router v3 を今まで使っていた人の参考になればと思います。
[React Router v4 からの主な移行・変更点一覧](https://qiita.com/TsutomuNakamura/items/b202bc7ebb2693937c13 "React Router v4 からの主な移行・変更点一覧")

# 参考 
- REACT JS TUTORIAL #7 - React Router Params & Queries
    - https://www.youtube.com/watch?v=ZBxMljq9GSE

- Start Bootstrap
    - https://startbootstrap.com

- BootstrapCDN
    - https://www.bootstrapcdn.com/bootswatch/

- react router v^4.0.0 Uncaught TypeError: Cannot read property 'location' of undefined
    - https://stackoverflow.com/questions/42892488/react-router-v4-0-0-uncaught-typeerror-cannot-read-property-location-of-unde

- Using React IndexRoute in react-router v4
    - https://stackoverflow.com/questions/42748727/using-react-indexroute-in-react-router-v4

- Nested Routes in React Router v4
    - https://stackoverflow.com/questions/42095600/nested-routes-in-react-router-v4

- Warning: Unknown DOM property class. Did you mean className?
    - https://stackoverflow.com/questions/30968113/warning-unknown-dom-property-class-did-you-mean-classname

- The prop 'history' is marked as required in 'Router', but its value is 'undefined'. in Router
    - https://stackoverflow.com/questions/43008036/the-prop-history-is-marked-as-required-in-router-but-its-value-is-undefine

- React Router failed prop 'history', is undefined
    - https://stackoverflow.com/questions/42845303/react-router-failed-prop-history-is-undefined

- React-router v4 this.props.history.push(…) not working
    - https://stackoverflow.com/questions/44312437/react-router-v4-this-props-history-push-not-working/44312810

- How to push to History in React Router v4?
    - https://stackoverflow.com/questions/42701129/how-to-push-to-history-in-react-router-v4

- Migrating from v2/v3 to v4
    - https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md