React Routerでルーティングを実装しよう

# 目次

- [概要](#概要)
- [React Routerとは](#react-routerとは)
- [React Routerの仕組み](#react-routerの仕組み)
  - [Routerの種類](#routerの種類)
  - [exactの役割](#exactの役割)
  - [Linkコンポーネント](#linkコンポーネント)
  - [Switchコンポーネント](#switchコンポーネント)
- [Intentional NavigationとProgrammatic Navigation](#intentional-navigationとprogrammatic-navigation)
- [まとめ](#まとめ)


# 概要
この記事では、ReactによるWebアプリケーションで**React Router**というライブラリを用いてルーティングを行う方法について説明しています。この記事を読んで、React Routerについて少しでも理解を深めていただけるようであれば幸いです。

### 前提条件

- アプリケーションの作成にはCreate React Appを用いている
- Reactに関してある程度の知識がある(Reactの基礎については[こちらの記事](https://qiita.com/jima-r20/items/48220ec1980535280a66)にもまとめていますので併せて参考にしてみてください)

### 環境

| 導入ライブラリ        | version |
|:------------------|--------:|
| create-react-app  | 3.4.1   |
| react             | 16.3.1  |
| react-dom         | 16.3.1  |
| react-router-dom  | 5.2.0   |


# React Routerとは
React Routerとは、Reactを用いて作成したSPA(Single Page Application)に対して、UIとURLを対応づけるためのライブラリです。
例えばTodoアプリの場合、`http://sample.com/login`にアクセスした時にはログインページを表示する`Login`コンポーネントをレンダリングし、`http://sample.com/todo`にアクセスした時にはTodo一覧を表示する`TodoList`コンポーネントをレンダリングするといった操作が可能になります。
![about-react-router.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622836/66448e5b-b0de-3823-db17-bedb7b4c858a.png)

### ライブラリ
React Routerを使用するためには、作成しているアプリケーションにライブラリのインストールが必要です。React Routerに関係するライブラリにはいくつか種類があるので、用途に合わせてどのライブラリが必要なのか抑えておく必要があります。

| ライブラリ              | 説明                                           |
|:---------------------|:----------------------------------------------|
| react-router         | ナビゲーションするためのコアライブラリ                   |
| **react-router-dom** | DOMベースアプリのナビゲーションを行うためのライブラリ       |
| react-router-native  | ネイティブアプリのナビゲーションを行うためのライブラリ        |
| react-router-redux   | ReduxとReact Routerをバインディングするためのライブラリ(`<Link>`などで事足りる場合や`history`に触れる状況を作ってある場合は不要)|

今回の場合はDOMベースのWebアプリケーションでのルーティングを行うため、`react-router-dom`を使用します。
react-router-domはコアライブラリであるreact-routerの上位互換のようなものなので、基本的にはreact-router-domを使用すれば大丈夫でしょう。

# React Routerの仕組み
はじめにコードの書き方を見てみましょう

```react:App.js
import React from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';

import Top from './components/Top';
import Login from './components/Login';
import TodoList from './components/TodoList';

const App = () => {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path='/' component={Top} />
        <Route path='/login' component={Login} />
        <Route path='/todo' component={TodoList} />
      </Switch>
      <Link to='/'>Back To Top</Link>
    </BrowserRouter>
  );
};
```

![mechanism-of-react-router.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622836/3a20c2e3-3886-b426-f79d-1558b9f9b337.png)

このように記述することで上図のようにpathによるルーティングを可能にできます。

ここで`history`とはオブジェクトで、JavaScriptが実行される場所であれば履歴の保存やナビゲートの管理などを行ってくれています。react-router-domの`BrowserRouter`を使用した場合にはこの`history`オブジェクトを自動的に作成してくれます。

### Routerの種類
上記で示した`BrowserRouter`は`Router`の上位コンポーネントでその他にもいくつか種類がありますのでここで紹介します。

| 種類            | 説明                                                   | URL例                   |
|:---------------|:-------------------------------------------------------|:----------------------:|
| BrowserRouter  | TLD(.jpとか.comとか)やportのあとの部分をすべてpathとして使用する  | localhost:3000/todo    |
| HashRouter     | #以降の全てをpathとして使用する                              | localhost:3000/#/todo  |
| MemoryRouter   | ナビゲートにURLを使用しない                                   | localhost:3000         |

他にも種類がありますので用途に応じて使い分けるようにするといいと思います。

### exactの役割
`<Route />`に`exact`を記述することで、`<Route />`内に記述されたpathがlocation.pathnameに完全に一致した場合にのみ該当するコンポーネントを返すようになります。
`exact`はデフォルトでは`false`ですが、`<Route />`に記述することで`true`となります。

[公式ドキュメント](https://reactrouter.com/web/api/Route/exact-bool)では以下のように記述されています。

| path  | location.pathname | exact  | matches? |
|:------|:------------------|:------:|:--------:|
| /one  | /one/two          | true   | no       |
| /one  | /one/two          | false  | yes      |

`exact`が`false`の場合、指定したpathが`/one`でもlocation.pathnameの`/one/two`に部分一致してしまうため表示されることになります。完全一致した場合のみ表示させたい時には、`exact`を記述しましょう。


[上記のコード](#react-routerの仕組み)を少し書き換えて以下のようにした場合を考えてみます。

```react
// 省略

return (
  // 省略
  // exactを使用しない場合
  <Route path='/' component={Top} />
  <Route path='/login' component={Login} />
  <Route path='/todo' component={TodoList} />
  // 省略
);
```
ローカル環境でアプリケーションを表示しているときに`http://localhost:3000/todo`にアクセスした場合、何が表示されるでしょうか。
**この場合、pathは`/todo`なので`TodoList`コンポーネントが表示されるのは明らかです。またpathには`/`も部分一致しているので、`Top`コンポーネントも同時に表示されます。**

次に`exact`を使用した場合を考えます。

```react
// 省略

return (
  // 省略
  // exactを使用した場合
  <Route exact path='/' component={Top} />
  <Route path='/login' component={Login} />
  <Route path='/todo' component={TodoList} />
  // 省略
);
```
**この場合、`path='/'`に`exact`を使用しているので`Top`コンポーネントはpathが`'/'`に完全一致した場合のみ表示されることになります。**そのため`http://localhost:3000/todo`にアクセスした場合には、`TodoList`コンポーネントのみが表示されます。

![exact.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622836/b9104ac1-6d27-ba21-667d-a4bbb77a714d.png)


### Linkコンポーネント
`<Link>`コンポーネントはHTMLでいう`<a>`タグとほぼ同じです。
ただし`<Link>`はプロパティとして関数を記述できたりとか色々できることが`<a>`タグと比較して多いようです。
詳しくは[公式ドキュメント](https://reactrouter.com/web/api/Link)を見ていただければいいと思います。

基本的な使い方を見てみましょう。

```react
<Link to='/'>Back To Top</Link>
```
この`<Link>`の中で`to`にpathを指定することで、ここのリンクをクリックしたときにこのpathが含まれている`<Route>`のコンポーネントを表示することができます。
今回の場合は`<Route path='/' component={Top} />`となっているので`Top`コンポーネントが表示されることになります。


### Switchコンポーネント
続いて`<Switch>`コンポーネントについて紹介します。

はじめに以下の場合どのようになるか考えてみます。
`path='/todo/new'`でTodoの新規作成画面を表示、`path='/todo/:id'`で`id`に応じたTodoの個別の詳細画面を表示したいとき、今まで紹介した内容で記述すると以下のようになると思います。

```react:App.js
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';

import NewTodo from './components/NewTodo';
import ShowTodo from './components/ShowTodo';

const App = () => {
  return (
    <BrowserRouter>
      <Route path='/todo/new' component={NewTodo} />
      <Route path='/todo/:id' component={ShowTodo} />
    </BrowserRouter>
  );
};
```
このとき`path='/todo/:id'`という記述方法が見慣れないかもしれませんが、このように記述することで**`:id`の部分には何でも入れることができます。**今回はTodoの`id`に応じて個別の画面を表示したいので、期待されるpathは`path='/todo/1'`とか`path='/todo/2'`のようなものです。

しかしここには問題点があります。
上述したように**「`:id`には何でも入る」**ということに注目しましょう。今回の場合では、新規作成画面`NewTodo`コンポーネントとTodo個別詳細画面`ShowTodo`コンポーネントのpathがそれぞれ`/todo/...`となっています。
つまり**`:id`の部分には`new`も入ってしまう**ということです。したがって、Todoの新規作成画面を表示したときには`NewTodo`コンポーネントと`ShowTodo`コンポーネントの両方が表示されることになります。

このようなときに使用するのが`<Switch>`です。
では書き方を見てみましょう。

```react
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';

import NewTodo from './components/NewTodo';
import ShowTodo from './components/ShowTodo';

const App = () => {
  return (
    <BrowserRouter>
      <Switch>
        <Route path='/todo/new' component={NewTodo} />
        <Route path='/todo/:id' component={ShowTodo} />
      </Switch>
    </BrowserRouter>
  );
};
```
このように記述することで、`<Switch>...</Switch>`の中に入っている`<Route>`コンポーネントから**１つだけを表示できる**ようになります。

ただしここで注意点があります。
`<Switch>...</Switch>`の内部では上から順に見ていくので**`<Route>`の配置順が重要**になります。上記のコードの場合、pathに`/todo/new`が指定されたときには`NewTodo`コンポーネントが表示されます。
しかし下記のように配置順を逆にした場合、

```react
<Switch>
  <Route path='/todo/:id' component={ShowTodo} />
  <Route path='/todo/new' component={NewTodo} />
</Switch>
```
**`:id`には何でも入る**のでpathに`/todo/new`を指定したときには先に上の`<Route>`が反応してしまうので、`ShowTodo`コンポーネントが表示されることになります。

もし`<Switch>`を使用しているときに、レンダリングがうまくいかないときには`<Route>`の配置順を見直してみるといいかもしれません。

# Intentional NavigationとProgrammatic Navigation
Intentional NavigationとProgrammatic Navigationについて簡単に説明します。

- **Intentional Navigation**:ユーザが`<Link>`をクリックしたとき移動
- **Programmatic Navigation**:アプリを介してユーザを強制的に移動するコードを実行

Intentional Navigationについてはここまでに説明した内容で実装できると思いますが、Programmatic Navigationについてはもう少し詳しく見ていく必要があります。

関数などで何か処理をさせた後にページを移動させる事例としては、Reduxを使用している場合に多いと思います。何かのボタンを押したときにActionを実行し、ReducerでStore内のState操作を行った後にページを移動させるなどの処理が考えられます。このような場合にはProgrammatic Navigationにする必要があります。

ここで関係してくるのが`history`オブジェクトです。ページ遷移の仕方には`<Link to='/path'>`をクリックする場合と、`history.push('/path')`のように関数として実行することができます。
これまでの内容では`<BrowserRouter>`を使用して自動的に`history`を作成していましたが、**`history.push()`などを扱うためには`history`を自身で用意する必要があります**。

では実際の使い方を見てみましょう。

```react:src/history.js
import { createBrowserHistory } from 'history';

export default createBrowserHistory();
```
`history`オブジェクトの作成はこれだけです。あとは`history.push()`などを扱いたいファイルでインポートすることで使用できます。

次に`<Route>`などを記述している`App.js`について見てみます。

```react:src/App.js
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';

import Top from './components/Top';
import Login from './components/Login';
import TodoList from './components/TodoList';
import history from `history`;  // これ重要

const App = () => {
  return (
    <Router history={history}>
      <Switch>
        <Route exact path='/' component={Top} />
        <Route path='/login' component={Login} />
        <Route path='/todo' component={TodoList} />
      </Switch>
    </Router>
  );
};
```
ここでは`history`オブジェクトを自身で作成しているので、`<BrowserRouter>`ではなく`<Router>`を使用します。そして`<Router history={history}>`と記述することで内部で示しているpathと`history.push()`などで指定するpathを関連付けてあげることができます。

ここで気がつくかもしれませんが**`<BrowserRouter>`は`Router`のwrapper**だったということがわかりますね。`BrowserRouter`では内部で同じことをやっています。([BrowserRouterのソースコードはこちら](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/BrowserRouter.js))

あとは処理としてページ遷移を行わせたいところで、

```react
import history from 'history';

// ...
// いろいろな処理
history.push('/todo')
```
みたいな感じで使用すれば記述してあげればOKです。**`history`のインポートだけ忘れないようにしましょう**。

# まとめ
少し長めの記事になってしまいましたが、今回はReact Routerの基本的な使い方について説明しました。
簡単にまとめたいと思います。

- **React Routerとは**
  - Reactを用いて作成したSPAに対して、UIとURLを対応づけるためのライブラリ
- **React Routerの仕組み**
  - Routerにもいくつか種類がある
  - `path`や`history`オブジェクトを巧みに使用する
  - `exact`や`<Switch>`などの使い方次第でルーティング処理が変わるので注意が必要
- **Intentional NavigationとProgrammatic Navigation**
  - 処理としてページ遷移を組み込む場合には`history.push()`

今回紹介した内容以外にも、React Routerには様々なAPIがあるので用途に合わせた使い方をしてみてください。

# 参考資料

- [React Router公式](https://reactrouter.com/web/guides/quick-start)
- [今から始めるReact入門 〜 React Router](https://qiita.com/TsutomuNakamura/items/34a7339a05bb5fd697f2)
- [react-routerとreact-router-domの違い](https://qiita.com/koja1234/items/486f7396ed9c2568b235)
- [history (GitHub)](https://github.com/ReactTraining/history/tree/master/docs)
- [BrowserRouter (Github)](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/BrowserRouter.js)
- [React Routerでページ制御](https://www.wakuwakubank.com/posts/733-react-router/)