【React】React Routerを使って簡単に画面遷移をしよう

# Reactで画面遷移をしよう

Reactで画面遷移をすることが多くあるので方法をまとめていきます。
Reactの画面遷移には、```React Router```を使うと簡単に作成することができます。
基本的なルーティング設定の書き方と、分割方法などまとめます。

https://reactrouter.com/

## React Routerの導入

create-react-appでプロジェクトを作成した後、はじめにReactRouterのパッケージをインストールします。

```sh
$ yarn add react-router-dom
```

## 画面遷移するためのページを用意しよう

試しに画面遷移先を作成したいと思います。今回はHomeとPage１〜Page３を用意しました。

```react:/react-basic/src/components/Home.jsx
export const Home = () => {
  return (
    <div>
      <h1>Home</h1>
    </div>
  );
};

```

```react:/react-basic/src/components/Page1.jsx
export const Page1 = () => {
  return (
    <div>
      <h1>Page1</h1>
    </div>
  );
};

```

```react:/react-basic/src/components/Page2.jsx
export const Page2 = () => {
  return (
    <div>
      <h1>Page2</h1>
    </div>
  );
};

```

```react:/react-basic/src/components/Page3.jsx
export const Page3 = () => {
  return (
    <div>
      <h1>Page3</h1>
    </div>
  );
};

```

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

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);
```

## 基本的な画面遷移

App.jsxにインストールしたReact Routerを使ってルーティングの設定を追加します。
```react-router-dom```のインポートを追加し、```BrowserRouter```と```Link```と```Switch```と```Route```を使えるようにします。
BrowserRouterでルーティングする設定を囲んであげます。
LinkはHTMLでいうとaタグのようなものです。to=""の中にパスを指定してあげます。
SwitchとRouteはルーティングするコンポーネントを指定します。exactをつけると完全一致になります。Homeはexactをつけてあげます。つけないと、```/```配下のすべてが対象になってしまいます。



```react:/react-basic/src/App.jsx

// react-router-domのインポートを追加
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";

import { Home } from "./components/Home";
import { Page1 } from "./components/Page1";
import { Page2 } from "./components/Page2";
import { Page3 } from "./components/Page3";

function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
        <br />
        <Link to="/page3">Page3</Link>
        <br />

        <Switch>
          {/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/page1">
            <Page1 />
          </Route>
          <Route path="/page2">
            <Page2 />
          </Route>
          <Route path="/page3">
            <Page3 />
          </Route>
        </Switch>
      </div>
    </BrowserRouter>
  );
}

export default App;
```

簡単に画面遷移させることができるようになりました。

|![FireShot Capture 088 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/a301a820-51ba-738b-97c0-0ab1272658b9.png)|
|:--|

## ネストされたページ遷移

先程作成したPage１からネストされたページ遷移を作成したいと思います。今回は、Page１DetailAとPage１DetailBのコンポーネントを２つ用意しました。

```react:/react-basic/src/components/Page1DetailA.jsx
export const Page1DetailA = () => {
  return (
    <div>
      <h1>Page1DetailA</h1>
    </div>
  );
};
```

```react:/react-basic/src/components/Page1DetailB.jsx
export const Page1DetailB = () => {
  return (
    <div>
      <h1>Page1DetailB</h1>
    </div>
  );
};
```

Page1に```import { Link } from "react-router-dom"```を追加し、遷移先のリンクを記載します。


```react:/react-basic/src/components/Page1.jsx
import { Link } from "react-router-dom";

export const Page1 = () => {
  return (
    <div>
      <h1>Page1</h1>
      <Link to="/page1/detailA">DetailA</Link>
      <br />
      <Link to="/page1/detailB">DetailB</Link>
    </div>
  );
};
```

最後にルーティング設定を追加します。

```react:react:/react-basic/src/App.jsx
// react-router-domのインポートを追加
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";

import { Home } from "./components/Home";
import { Page1 } from "./components/Page1";
import { Page2 } from "./components/Page2";
import { Page3 } from "./components/Page3";
import { Page1DetailA } from "./components/Page1DetailA";
import { Page1DetailB } from "./components/Page1DetailB";

function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
        <br />
        <Link to="/page3">Page3</Link>
        <br />

        <Switch>
          {/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
          <Route exact path="/">
            <Home />
          </Route>
          <Route
            path="/page1"
            render={() => (
              <Switch>
                <Route exact path="/page1">
                  <Page1 />
                </Route>
                <Route exact path="/page1/detailA">
                  <Page1DetailA />
                </Route>
                <Route exact path="/page1/detailB">
                  <Page1DetailB />
                </Route>
              </Switch>
            )}
          />
          <Route path="/page2">
            <Page2 />
          </Route>
          <Route path="/page3">
            <Page3 />
          </Route>
        </Switch>
      </div>
    </BrowserRouter>
  );
}

export default App;

```

実行し、確認します。ネストされたページ遷移ができました。


|![FireShot Capture 089 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/a9c272ee-be86-b251-32bc-bb62c4d348f4.png)|
|:--|

page1を複数書くと混乱につながるので、propsで受け取ったurlを使うように変更すると、下記のように変更することができます。


```react:/react-basic/src/App.jsx
// react-router-domのインポートを追加
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";

import { Home } from "./components/Home";
import { Page1 } from "./components/Page1";
import { Page2 } from "./components/Page2";
import { Page3 } from "./components/Page3";
import { Page1DetailA } from "./components/Page1DetailA";
import { Page1DetailB } from "./components/Page1DetailB";

function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
        <br />
        <Link to="/page3">Page3</Link>
        <br />

        <Switch>
          {/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
          <Route exact path="/">
            <Home />
          </Route>
          <Route
            path="/page1"
            render={({ match: { url } }) => ( // propsでurlを受け取る
              <Switch>
                <Route exact path={url}>
                  <Page1 />
                </Route>
                <Route exact path={`${url}/detailA`}>
                  <Page1DetailA />
                </Route>
                <Route exact path={`${url}/detailB`}>
                  <Page1DetailB />
                </Route>
              </Switch>
            )}
          />
          <Route path="/page2">
            <Page2 />
          </Route>
          <Route path="/page3">
            <Page3 />
          </Route>
        </Switch>
      </div>
    </BrowserRouter>
  );
}

export default App;

```

## ルーティング設定の分割

これまでルーティング設定をApp.jsxに書いてきましたが、コードの見通しを良くするためにルーティング設定の分割を行います。
src配下にrouterのフォルダを作成し、Router.jsxのファイルを作成します。これまでApp.jsxに書いていたルーティング設定を移動させます。

```react:/react-basic/src/components/router/Router.jsx
import { Switch, Route } from "react-router-dom";
import { Home } from "../Home";
import { Page1 } from "../Page1";
import { Page1DetailA } from "../Page1DetailA";
import { Page1DetailB } from "../Page1DetailB";
import { Page2 } from "../Page2";
import { Page3 } from "../Page3";

export const Router = () => {
  return (
    <Switch>
      {/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
      <Route exact path="/">
        <Home />
      </Route>
      <Route
        path="/page1"
        render={(
          { match: { url } } // propsでurlを受け取る
        ) => (
          <Switch>
            <Route exact path={url}>
              <Page1 />
            </Route>
            <Route exact path={`${url}/detailA`}>
              <Page1DetailA />
            </Route>
            <Route exact path={`${url}/detailB`}>
              <Page1DetailB />
            </Route>
          </Switch>
        )}
      />
      <Route path="/page2">
        <Page2 />
      </Route>
      <Route path="/page3">
        <Page3 />
      </Route>
    </Switch>
  );
};

```

```react:/react-basic/src/App.jsx
// react-router-domのインポートを追加
import { BrowserRouter, Link } from "react-router-dom";
import { Router } from "./components/router/Router";

function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
        <br />
        <Link to="/page3">Page3</Link>
        <br />
      </div>
      <Router />
    </BrowserRouter>
  );
}

export default App;
````

無事分けることができました！
実行して動作することが確認できました。

|![FireShot Capture 089 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/a9c272ee-be86-b251-32bc-bb62c4d348f4.png)|
|:--|


## 更にページごとに分割してみる

page1をさらに分割してみます。Page1Routes.jsxというファイルを作成し、オブジェクトとしてルーティング設定を記載します。

```react:/react-basic/src/components/router/Page1Routes.jsx
import { Page1 } from "../Page1";
import { Page1DetailA } from "../Page1DetailA";
import { Page1DetailB } from "../Page1DetailB";

export const page1Routes = [
  {
    path: "/",
    exact: true,
    children: <Page1 />,
  },
  {
    path: "/detailA",
    exact: false,
    children: <Page1DetailA />,
  },
  {
    path: "/detailB",
    exact: false,
    children: <Page1DetailB />,
  },
];

```

```react:/react-basic/src/components/router/Router.jsx
import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { Page2 } from "../Page2";
import { Page3 } from "../Page3";
import { page1Routes } from "./Page1Routes";

export const Router = () => {
  return (
    <Switch>
      {/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
      <Route exact path="/">
        <Home />
      </Route>
      <Route
        path="/page1"
        render={({ match: { url } }) => (
          <Switch>
            {page1Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />
      <Route path="/page2">
        <Page2 />
      </Route>
      <Route path="/page3">
        <Page3 />
      </Route>
    </Switch>
  );
};

```

無事分けることができました。ここまで分割すると、見通しが良くなりますね。

|![FireShot Capture 089 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/a9c272ee-be86-b251-32bc-bb62c4d348f4.png)|
|:--|

## URLパラメーターを扱う

```import { useParams } from "react-router-dom";```を使うことで、URLパラメーターを使うことができます。
Page2をURLパラメーターを使うように変更していきます。
pathに```:id```のように```:```を使うことでパラメーターを扱うことができます。


```react：/react-basic/src/components/router/Page2Routes.jsx
import { Page2 } from "../Page2";
import { UrlParameter } from "../UrlParameter";

export const page2Routes = [
  {
    path: "/",
    exact: true,
    children: <Page2 />,
  },
  {
    path: "/:id", //URLパラメーターを使う
    exact: false,
    children: <UrlParameter />,
  },
];
```

```useParams```をインポートし、idを受け取るように変更します。

```react:/react-basic/src/components/UrlParameter.jsx
import { useParams } from "react-router-dom";

export const UrlParameter = () => {
  const { id } = useParams(); //URLパラメーターのIDを受け取る
  return (
    <div>
      <h1>UrlParameter</h1>
      <p>パラメーターは{id}です</p>
    </div>
  );
};

```

Linkでパラメーターの100を渡すように変更します。

```react:/react-basic/src/components/Page2.jsx
import { Link } from "react-router-dom";

export const Page2 = () => {

  return (
    <div>
      <h1>Page2</h1>
      <Link to="/page2/100">URL Parameter</Link>

    </div>
  );
};
```



```react:/react-basic/src/components/router/Router.jsx
import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { Page3 } from "../Page3";
import { page1Routes } from "./Page1Routes";
import { page2Routes } from "./Page2Routes";

export const Router = () => {
  return (
    <Switch>
      {/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
      <Route exact path="/">
        <Home />
      </Route>
      <Route
        path="/page1"
        render={({ match: { url } }) => (
          <Switch>
            {page1Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />
      <Route
        path="/page2"
        render={({ match: { url } }) => (
          <Switch>
            {page2Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />
      <Route path="/page3">
        <Page3 />
      </Route>
    </Switch>
  );
};
```

パラメーターを受け取ることができるようになりました。

|![FireShot Capture 090 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/edfdf36b-4e0a-99fb-d487-94bf63cfcdcd.png)|
|:--|



## クエリパラメータを扱う

```import { useLocation } from "react-router-dom";```をインポートすることクエリパラメータを扱うことができます。```const { search } = useLocation();```でクエリパラメータを受け取る事ができます。
では実際に、Page２に書いていきます。

```react:/react-basic/src/components/UrlParameter.jsx
import { useParams, useLocation } from "react-router-dom";

export const UrlParameter = () => {
  const { id } = useParams(); //URLパラメーターのIDを受け取る
  const { search } = useLocation(); // クエリパラメータを受け取る
  const query = new URLSearchParams(search);
  return (
    <div>
      <h1>UrlParameter</h1>
      <p>パラメーターは{id}です</p>
      <p>クエリは{query.get("name")}です</p> 
    </div>
  );
};
```

queryページへのリンクを追加します。

```react:/react-basic/src/components/Page2.jsx
import { Link } from "react-router-dom";

export const Page2 = () => {
  return (
    <div>
      <h1>Page2</h1>
      <Link to="/page2/100">URL Parameter</Link>
      <br />
      <Link to="/page2/100?name=hoge">Query Parameter</Link>
    </div>
  );
};
```

## ページ遷移する際に状態を渡す

Linkのtoの箇所をオブジェクトで書くことができ、stateに状態を渡すことができます。

```react:/react-basic/src/components/Page1.jsx
import { Link } from "react-router-dom";

export const Page1 = () => {
  const arr = [...Array(100).keys()]; // 受け渡し用のダミーデータ作成

  return (
    <div>
      <h1>Page1</h1>
      <Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>
      <br />
      <Link to="/page1/detailB">DetailB</Link>
    </div>
  );
};
```

```const { state } = useLocation();```で渡された状態を取得することができます。

```react
import { useLocation } from "react-router-dom";

export const Page1DetailA = () => {
  const { state } = useLocation();
  console.log(state);
  return (
    <div>
      <h1>Page1DetailA</h1>
    </div>
  );
};
```

無事状態が渡されていることが確認できました。

|![スクリーンショット 2021-10-07 23.15.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/f417bd51-b41d-bcda-d33c-684b6f95057f.png)|
|:--|

## useHistoryを使って画面遷移

useHistoryを使うことで、Linkを使わずに画面遷移させることができます。


```react:/react-basic/src/components/Page1.jsx
import { Link, useHistory } from "react-router-dom";

export const Page1 = () => {
  const arr = [...Array(100).keys()]; // 受け渡し用のダミーデータ作成

  const history = useHistory(); // historyを用意する

  const onClickButton = () => {
    history.push("/page1/detailA"); // 画面遷移を書く
  };

  return (
    <div>
      <h1>Page1</h1>
      <Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>
      <br />
      <Link to="/page1/detailB">DetailB</Link>
      <br />
      <button onClick={onClickButton}>DetailA</button>
    </div>
  );
};
```

戻るときは、```goback()```を使います。

```react:/react-basic/src/components/Page1DetailA.jsx
import { useLocation, useHistory } from "react-router-dom";

export const Page1DetailA = () => {
  const { state } = useLocation();

  const history = useHistory(); // historyを用意する

  const onClickButton = () => {
    history.goBack(); // 戻ることができる
  };

  return (
    <div>
      <h1>Page1DetailA</h1>
      <button onClick={onClickButton}>戻る</button>
    </div>
  );
};

```

## 404ページを作成してみる

はじめに４０４ページを作成します。

```react:/react-basic/src/components/UrlParameter.jsx
export const Page404 = () => {
  return (
    <div>
      <h1>Page404</h1>
    </div>
  );
};
```

ルーティング設定の一番下に、```path="*"```を追加し、pathに一致しなかった場合すべて404ページが表示されるようします。

```react:/react-basic/src/components/router/Router.jsx
import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { Page3 } from "../Page3";
import { Page404 } from "../Page404";
import { page1Routes } from "./Page1Routes";
import { page2Routes } from "./Page2Routes";

export const Router = () => {
  return (
    <Switch>
      {/* exactをつけると完全一致になります。Homeはexactをつけてあげます */}
      <Route exact path="/">
        <Home />
      </Route>
      <Route
        path="/page1"
        render={({ match: { url } }) => (
          <Switch>
            {page1Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />
      <Route
        path="/page2"
        render={({ match: { url } }) => (
          <Switch>
            {page2Routes.map((route) => (
              <Route
                key={route.path}
                exact={route.exact}
                path={`${url}${route.path}`}
              >
                {route.children}
              </Route>
            ))}
          </Switch>
        )}
      />
      <Route path="/page3">
        <Page3 />
      </Route>

      {/* 404ページ追加 */}
      <Route path="*">
        <Page404 />
      </Route>
    </Switch>
  );
};

```

404ページが表示されるようになりました。

|![FireShot Capture 093 - React App - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95954/58db6b8c-367b-874b-f859-0836053dad05.png)|
|:--|