【React】Routerを使った画面遷移

React のルーティングについて、こちらのUdemy教材を活用して学んだことをまとめました。

Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版

Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版
「React何となく分かったけど次どうしたら良いか分からない」という人がステップアップするために知っておくべきことを順序立ててハンズオン形式で詰め込みました。本コースを終える頃にはもっとReactのことを好きになっていると思います。

React のルーティングについて の公式ドキュメントはこちら

https://reactrouter.com/docs/en/v6/examples/basic#preview

それでは行きましょう!

ルーティングが必要な理由

ルーティングについて学ぶ前に、SPAであるReactにおいて、なぜ、ルーティングを使う必要があるのでしょうか。

その理由は、ブラウザの履歴や、ブックマーク、「戻る」「進む」ボタン等の機能を実現するには、ルーティングライブラリによって、リクエストのエンドポイントを定義する必要があるためです。

react-router-domをinstall

react-router-domをinstallします。

これは、ブラウザで動作するReactアプリで使用されます。

$ npm install react-router-dom

TypeScriptを使用する場合

$ npm install @types/react-router-dom

アプリのベースを作成

画面遷移の確認をするために、まずはアプリのベースを作成します。

・react-router-domをimport

・Home, Page1, Page2コンポーネントを作成(3画面作ります。)

・App.jsで上記3つのコンポーネントを呼び出す

・BrowserRouterをimport
 これはこのコンポーネントで囲った配下でルーティングを有効にするといった働きをするもの

react-router-domのLinkを使う場合は、全体をBrowserRouterで囲う必要がある。

・Linkをimport

・Linkタグでパスを指定

ここまでのコードがこちら

import { BrowserRouter, Link } from "react-router-dom";

import { Home } from "./Home";
import { Page1 } from "./Page1";
import { Page2 } from "./Page2";
import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/Page1">Page1</Link>
        <Link to="/Page2">Page2</Link>
        <Home />
        <Page1 />
        <Page2 />
      </div>
    </BrowserRouter>
  );
}

・Switchをimport
 Switchタグで、どのパスのときにどのコンポーネントに飛ばすかを指定する。

・Routeをimport
 Routeタグで囲ってpathを指定し、そこに表示したいコンポーネントを入れる。

import { BrowserRouter, Link, Switch, Route } from "react-router-dom";

import { Home } from "./Home";
import { Page1 } from "./Page1";
import { Page2 } from "./Page2";
import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/Page1">Page1</Link>
        <Link to="/Page2">Page2</Link>
      </div>
      <Switch>
        <Route path="/Page1">
          <Page1 />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

こうすると以下のエラー

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports. Check the render method of `App`.

これは、React Routerのバージョン変更によるものです。

Routeの部分を以下のように書き換えることにより解決!

import { BrowserRouter, Link, Routes, Route } from "react-router-dom";

import { Home } from "./Home";
import { Page1 } from "./Page1";
import { Page2 } from "./Page2";
import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/Page1">Page1</Link>
        <Link to="/Page2">Page2</Link>
      </div>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/Page1" element={<Page1 />} />
        <Route path="/Page2" element={<Page2 />} />
      </Routes>
    </BrowserRouter>
  );
}

“react-router-dom”のバージョン5→6への変更に伴う変更点

Switchが使用できなくなったのと、Routesの追加、Routeの書き方。

変更部のみ抽出すると

      <Switch>
        <Route path="/Page1">
          <Page1 />
        </Route>
      </Switch>

     ↓

      <Routes>
        <Route path="/Page1" element={<Page1 />} />
      </Routes>

詳細はこちらのドキュメントに記載されています。

https://reactrouter.com/docs/en/v6/upgrading/v5#relative-routes-and-links

これで、画面遷移の実装をしていくためのベースができました!

これ以降はバージョンを”react-router-dom”: “5.3.0”として進めます。

ネストされたページへの遷移

ネストされたページヘの遷移をするためのルーティングの書き方についてです。

Page1配下にdetailAとdetailBのpathを作成し実装していきます。

・Page1の詳細画面AとPage1の詳細画面Bのコンポーネントをそれぞれ、Page1DetailAとPage1DetailBという名前で作成する。

・Page1にdetailAとdetailBのLinkを作成

・Linkをimport

import { Link } from "react-router-dom";

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

Routeは2通りの書き方がある。

アロー関数を使わない書き方
<Route path="/Page1">
  <Page1 />
</Route>

/Page1というpathでPage1のコンポーネントを呼び出す。

アロー関数を使った書き方
<Route path="/Page1" render={() => <Page1 />} />

renderのアロー関数の中身にPage1を返却する。

ここにネストルーティングを書いていく。

するとこうなる。

        <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="/Page1">
          <Switch>
            <Route exact path="/page1">
              <Page1 />
            </Route>
            <Route exact path="/page1/detailA">
              <Page1DetailA />
            </Route>
            <Route exact path="/page1/detailB">
              <Page1DetailB />
            </Route>
          </Switch>
        </Route>

ネストの親を保証する

現状、pathをpage1/と記述しているが、このpage1/を書かなくてもいいようにしたい。

Page1配下はPage1であることを保証する方法があります。

Page1リンクをクリックした時のリンク先urlは、matchというobjectの中のurlに記述されている。

そのため以下のように記述するとpathが取り出せる。

{ match: { url } }

        <Route
          path="/Page1"
          render={({ match: { url } }) => (
            <Switch>
              <Route exact path={url}>
                <Page1 />
              </Route>
              <Route exact path={`${url}/detailA`}>
                <Page1DetailA />
              </Route>
              <Route exact path={`${url}/detailB`}>
                <Page1DetailB />
              </Route>
            </Switch>
          )}
        />

こうすることで、タイポが理由でリンクを間違うといったことがなくなる。

ルート定義の分割

ルーティングも、量が増えてくると、見にくいコードとなってしまいます。

そこでAppコンポーネントからルーティング部を別コンポーネントに分割、さらにその中のPage1を別コンポーネントに分割する方法について見ていきます。

・src > routerディレクトリを作成する。

ルーティングの設定は、src配下にrouterディレクトリを作成し、Router.sxファイルにするのが一般的です。

・Router.jsxを作成し、ここにRouteを移管し、Routerという名前でコンポーネント化する。

こうすることでApp.jsがスッキリする!

import { BrowserRouter, Link } from "react-router-dom";
import { Router } from "./router/Router.jsx";

import "./styles.css";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <Link to="/Page1">Page1</Link>
        <Link to="/Page2">Page2</Link>
      </div>
      <Router />
    </BrowserRouter>
  );
}

import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { Page1 } from "../Page1";
import { Page1DetailA } from "../Page1DetailA";
import { Page1DetailB } from "../Page1DetailB";
import { Page2 } from "../Page2";

export const Router = () => {
  return (
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>

      <Route
        path="/Page1"
        render={({ match: { 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>
    </Switch>
  );
};

ここから更にPage1を切り出す。

・router > Page1Routes.jsxを作成

・配列を作る

以下の部分を書き換える。

            <Route exact path={url}>
              <Page1 />
            </Route>
            <Route exact path={`${url}/detailA`}>
              <Page1DetailA />
            </Route>
            <Route exact path={`${url}/detailB`}>
              <Page1DetailB />
            </Route>

できたのがこちら

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 />
  }
];

pathとexactとchildrenであるコンポーネントを切り分けて配列の形として、定数page1Routesにまとめる。

その定数をmapで展開する。

import { Route, Switch } from "react-router-dom";
import { Home } from "../Home";
import { page1Routes } from "./Page1Routes";
import { Page2 } from "../Page2";

export const Router = () => {
  return (
    <Switch>
      <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>
    </Switch>
  );
};

URLパラメータを扱う

pathのidを取得してそのidのページを表示する方法について見ていきます。

・表示用のUrlParameter.jsxコンポーネントを作成する。

・Routeのコンポーネントを作成

URLパラメータの場合はpathの書き方がちょっと違います。

/:パラメータ名

今回は以下の形で使用する
path: "/:id"

パラメータを取得するには、useParamsというHooksを使います。

このuseParamsは、関数が実行されるとidを受け取ることができるもので、これで、渡されたidのpathを表示できるようになる。

まとめると、、

Linkのページ

import { Link } from "react-router-dom";

export const Page2 = () => {
  return (
    <div>
      <h1>Page2ページ</h1>
      <Link to="/page2/100">URL Parameter</Link>
    </div>
  );
};

Route設定のファイル

import { Route, Switch } from "react-router-dom";
import { page2Routes } from "./Page2Routes";

export const Router = () => {
  return (
    <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>
        )}
      />
    </Switch>
  );
};

上記のRouteのmapの展開

import { Page2 } from "../Page2";
import { UrlParameter } from "../UrlParameter";

export const page2Routes = [
  {
    path: "/",
    exact: true,
    children: <Page2 />
  },
  {
    path: "/:id",
    exact: false,
    children: <UrlParameter />
  }
];

pathのidを取得しているファイル

import { useParams } from "react-router-dom";

export const UrlParameter = () => {
  const { id } = useParams();
  return (
    <div>
      <h1>UrlParameterページです。</h1>
      {id}
    </div>
  );
};

画面遷移時の情報の受け渡し

画面遷移時に遷移元の情報を遷移先に渡す方法について見ていきます!

Page1の情報をPage1/DetailAに持っていく場合

・仮で100件の配列を作る。

const arr = [...Array(100).keys()];

これを画面遷移時に持っていきたい。

Linkを書き換える。

<Link to="/page1/detailA">DetailA</Link>

     ↓

<Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>

stateにarrを設定していることで、画面遷移時に、arrを持っていくことができる。

受け取り側ではuseLocationを使う。

DetailAに渡っているかconsoleで確認する。

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

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

consoleを確認すると、最初に作った100件の配列が渡っていることが確認できる。

Linkを使わないページ遷移

これまで、Linkを使用した遷移方法を見てきましたが、Linkを使用しないで、JavaScriptを使って遷移させる方法について見ていきます!

useHistoryというHookを使う。

・ボタンにonClickイベントを設定

・その関数の中でpushという関数を使い、その中にリンクpathを記述

import { useHistory } from "react-router-dom";

export const Page1 = () => {

  const history = useHistory();

  const onClickDetailA = () => history.push("/page1/detailA");

  return (
    <div>
      <h1>Page1ページ</h1>
      <button onClick={onClickDetailA}>detailAへのリンク</button>
    </div>
  );
};

多くの画面遷移はこれを使う!!

このuseHistoryもまた、v6へのバージョンアップに伴い、変更がされており、

useNavigateとなり、pushは不要に変更となっている。

詳細はこちら↓

https://reactrouter.com/docs/en/v6/upgrading/v5#use-usenavigate-instead-of-usehistory

まとめ

以上、画面遷移についてでした。

いろんなリンクの書き方があったり、リンクもコンポーネントで分けたり、更に、mapを活用して配列展開したりと、なかなか奥が深いですね!

ご覧いただきありがとうございました。

コメント