SPAにおけるルーティング〜React Router初歩

## はじめに

Reactで開発を進める上で必ずと言っていいほど使用されている`React Router`。

ドキュメントが充実しているので、目を通せば大体のことは、解決すると思いますが、使い方の整理の意味を込めて文章としてまとめます。

[公式ドキュメント | React Router](https://reactrouter.com/)

## 対象読者
React初学者

## SPAにおけるルーティングのおさらい
さらっとですが、SPAにおけるルーティングについて整理します。
※ここら辺、理解してる人は読み飛ばして下さい。

SPAにおけるルーティングを一言で説明すると、**『DOMの動的な書き換えによってページ遷移を擬似的に実現するとともに、ブラウザのセッション履歴をそれに同期させる事』** です。

SPAはサーバーへの初回リクエストに対して、アプリ全体が記述されたJSのコードと、そこで使用されるアセットファイルがごそっと返されます。

以降、ページ遷移はアプリが動的にDOMを書き換えることで、移動してるように見せてるに過ぎない点が大きな特徴です。即ち、ブラウザのアドレスバーのURLが書き換わっても、実際にサーバーへリクエストを投げることは原則的には無いという事です。

これは、リクエストされたURLに対して、そのURLに紐づく適切なページ内容をアプリケーションサーバーがクライアントに返すRailsやLaravelのようなフレームワークとは異なりますね。

では、SPAでルーティングを実現するために、どうしてるかというと、これには、[History API](https://developer.mozilla.org/ja/docs/Web/API/History_API)という技術が使われています。HTML5から導入された[__pushState__](https://developer.mozilla.org/ja/docs/Web/API/History/pushState)と[__replaceState__](https://developer.mozilla.org/ja/docs/Web/API/History/replaceState)で、ブラウザのセッション履歴に任意のURLを追加したり、特定の履歴を書き換えたりしながら、JSによって履歴のURLを制御することを可能にしています。

そして、SPAでのルーティングの適用単位はコンポーネントです。
RailsやLaravel等は、Controllerがルーティングに対応する名前を持つViewを選び、それを使用してレスポンスを返しますが、Reactはルートのコンポーネントから階層を下り、「ここから先はこのコンポーネントがマウントされる、このパスの時は別のコンポーネントがマウントされるといったようにコンポーネントそのものがルーティングとなります。

上記を意識しながら、次セクションで実際に試してみましょう。

## 実際にルーティングを定義してみる

まずはreact-router-domをインストールします。

```terminal
npm install react-router-dom
```

次にサンプルでいくつかコンポーネントを作っておきましょう。

```react:Home.tsx
export const Home = () => {
  return (
    <>
      <h1>Homeページです</h1>
    </>
  );
};
```

```react:Sample01.tsx
export const Sample1 = () => {
  return (
    <>
      <h1>Page01</h1>
    </>
  );
};
```

```react:Sample02.tsx
export const Sample02 = () => {
  return (
    <>
      <h1>Page02</h1>
    </>
  );
};
```

次に`src`直下に`route`ディレクトリを作成します。
その中に、ルーティングを定義していきます。

手順としては以下の通り
① ルーティング設定(URLとそれに対応するコンポーネントを決める)
② ①が決まったら、Linkタグを使用して、Linkを設置

コードを書く上で気をつけることは２点です。
・Router(BrowserRouter)の中にRouteを定義
・Routeタグにpathと、コンポーネントを定義

以下に例を示します。

```react:route/index.tsx(コンポーネントの読み込みは任意で)
import { React } from "react";
import { Route, Switch } from "react-router-dom";

import { Home } from "../component/reactRouter/Home";
import { Sample01 } from "../component/reactRouter/Sample01";
import { Sample02 } from "../component/reactRouter/Sample02";

export const RootRouter: React.VFC = () => (
  <Switch>
    <Route exact path="/">
      <Home />
    </Route>
    <Route exact path="/sample01">
      <Sample01 />
    </Route>
    <Route exact path="/sample02">
      <Sample02 />
    </Route>
  </Switch>
);
```


続いて、`App.tsx`に上記のルーティングを読み込んで、Linkタグでリンクを設置していきます。

```react:App.tsx
import * as React from "react";
import { RootRouter } from "./route";
import { Link, BrowserRouter as Router } from "react-router-dom";

function App(): React.VFC {
  return (
    <div className="App">
      <Router>
        <RootRouter />
        <Link to="/">Home</Link>
        <br />
        <Link to="/sample01">Page1</Link>
        <br />
        <Link to="/sample02">Page2</Link>
      </Router>
    </div>
  );
}

export default App;
```

上記で、一番シンプルな実装が実現しました。
![react-router.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33632/6e558560-2eb0-53b5-27bb-df2065abc289.gif)




## React Routerでパラメータを扱う

実案件では、URLパラメータを扱う場面は多々あります。以下にその方法を示します。

### URLパラメータ

例えば、UserIdをAPIの引数にして、UserIdに応じたページを取得するというケース等、動的に変化する部分をReactRouterでどのように扱うのでしょうか。

サンプルとして以下のルーティングの追加と新しいコンポーネントを作成します。UserId：１のユーザーがログインしている状態というケースを想定して、サンプルを書きます。
(※ APIは[JSONPlaceholder](https://jsonplaceholder.typicode.com/)を使わせてもらいます)

URLパラメータを扱う場合、パスの書き方として`/:id`といった書き方になります。`:id`の部分は任意で書くことが可能で、例えば、`/:userid`とかでも問題ありません。

```react:route/index.tsx(抜粋)
import { Profile } from "../component/reactRouter/Profile";

export const RootRouter: React.VFC = () => (
  <>
    {/* 以下コンポーネントを追加 */}
    <Route exact path="/user/:id">
      <Profile />
    </Route>
  </>
);
```

以下が新しく作成したコンポーネントです。
**URLパラメーターを扱いたい場合は、`useParams`を使用します。**これを使って、ルーティングで定義した`:id`の部分を抽出し、取得したidを元にAPIを叩いています。

```react:Profile.tsx
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";

export const Profile = () => {
  const [user, setUser] = useState(null);
  const { id } = useParams(); // useIdを取得

　// useParamsを使って取得したIDを使ってAPIを叩く
  useEffect(() => {
    const fetchUser = async () => {
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/users/${id}`
      );
      const userData = await res.json();
      await setUser(userData);
    };
    fetchUser();
  }, [id]);

  return (
    <>
      <h1>Profile Detail</h1>
      {user ? (
        <ul>
          <li>UserId: {user.id}</li>
          <li>UserName: {user.name}</li>
          <li>E-mail{user.email}</li>
          <li>Address: {user.address.street}</li>
        </ul>
      ) : (
        <>Loading</>
      )}
    </>
  );
};
```

最後に先ほど作成したHomeコンポーネントに遷移先を設置して、動作確認します。遷移先のパスは本来であれば、グローバルステートから、UserId情報を取得する形が一般的かと思いますが、サンプルとして`/user/1`をベタ書きしてます。(本来であれば`/user/:id`になる)

```React:Home.tsx
import { Link } from "react-router-dom";

export const Home = () => {
  return (
    <>
      <h1>Homeページです</h1>
      <div>
        <Link exact to="/user/1">
          Profile
        </Link>
      </div>
    </>
  );
};
```
![react-router2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33632/d50fe079-7165-2834-0286-e74d55f923d2.gif)

## 最後に
他にも便利なhooksが用意されているので、[公式ドキュメント](https://reactrouter.com/)に目を通しておくと良いかと思います。