Michi's Tech Blog

一人前のWebエンジニアを目指して

Reactキャッチアップへの道③ 『ルーティング』

こんにちは! スマレジ テックファームのMichiです!

前週に引き続き、Reactのキャッチアップをしていきます。今回は、『Reactのルーティング』について学びます。

はじめに

Reactでルーティングを設定するにあたって、『React Router』というライブラリを使用します。

reactrouter.com

下記コマンドで、ライブラリをインストールしてください。

npm install --save react-router-dom

※本記事ではReact Routerの最新バージョンである6系で解説を進めます。

また、ディレクトリ構造は以下のようになります。

- root
    - src
        - App.js
        - Home.jsx
        - Page1.jsx
        - Page2.jsx
        - index.js

それでは、はじめていきましょう。

基本的なルーティング

React Routerを使用するには、<BrowserRouter>を使います。<BrowserRouter>で囲った範囲でのみルーティングが有効になるので、ルートコンポーネントであるApp.jsの一番外側に<BrowserRouter>を適用します。

App.js

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

export default function App() {
  return (
    <BrowserRouter>
      <div className="App"></div>
    </BrowserRouter>
  );
}


<Link>タグを使用することで、ハイパーリンクの要素を作成することができます。要素をクリックすると、to属性で指定したパスへ遷移します。(HTMLの<a>タグと同じです。)

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

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


次に、パスとコンポーネントの紐づけを行います。

<Routes>タグを使うと、囲った範囲の中で、指定したパス(URL)に紐づいたコンポーネントの切り替えができます。

<Routes>配下では、<Route>タグを使って、パスとコンポーネントの紐づけを行います。path属性にパス(URL)、element属性にコンポーネントを指定します。

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

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

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

これで、各コンポーネントのリンクをクリックしたとき、対象のコンポーネントへ遷移することができるようになります。

Not Foundページの設定

現状、先ほど指定した以外のURLにアクセスすると、各コンポーネントへのリンク以外、画面に何も表示されません。

存在しないパス"/hoge"にアクセス

これではユーザーに混乱を与えてしまいますので、専用の『Not Found』ページを作成します。 まずは、『Not Found』用のコンポーネントの作成からです。

NotFound.jsx

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

export const NotFound = () => {
  return (
    <div>
      <h1>ページが見つかりません</h1>
      <Link to="/">TOPに戻る</Link>
    </div>
  );
};


次に、App.jsNotFound.jsxへのルーティングを追加します。最初に設定したどのルーティングにも該当しない場合、 『Not Found』ページを出したいので、ルーティングの一番下にpath="*"で指定した<Route>タグを配置します。

App.js

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

import { Home } from "./Home";
import { NotFound } from "./NotFound";
import { Page1 } from "./Page1";
import { Page2 } from "./Page2";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/page1" element={<Page1 />} />
        <Route path="/page2" element={<Page2 />} />
        {/* 追加 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

これで、どのルーティングにも該当しない場合、『Not Found』ページを表示することができました。

ネストされたルーティング

/page1/detailのようなネストされたルーティングを作成します。まずは、Detail.jsxコンポーネントを作成します。

Detail.jsx

export const Detail = () => {
  return (
    <div>
      <h1>Page1のDetailです</h1>
    </div>
  );
};

次に、/page1/detailのルーティングを追加します。

App.js/pageへのルーティングを設定している箇所に、ネストしてルーティングを追加します。パスは親ルートからの続きで書くので、path="detail"とします。(/がいらないことに注意!)

App.js

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

import { Home } from "./Home";
import { NotFound } from "./NotFound";
import { Page1 } from "./Page1";
import { Page2 } from "./Page2";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      <Routes>
        <Route path="/" element={<Home />} />
        {/* ネストされるルーティングを追加 */}
        <Route path="/page1" element={<Page1 />}>
          <Route path="detail" element={<Detail />} />
        </Route>
        <Route path="/page2" element={<Page2 />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

最後に、Page1.jsx/page1/detailへのリンクと、<Outlet>タグを追加します。<Outlet>は親ルートのコンポーネントに、その子ルートのコンポーネントをどこへレンダリングするか伝えるための要素です。<Outlet>がない場合は正しくレンダリングされないので、注意しましょう。

Page1.js

import { Link, Outlet } from "react-router-dom"; //追加

export const Page1 = () => {
  return (
    <div>
      <h1>Page1です</h1>
      {/* 追加 */}
      <Link to="/page1/detail">Detail</Link>
      <Outlet />
    </div>
  );
};

これで、ネストされたルーティングも追加することができました。

ルーティング定義の切り分け

さて、今までApp.jsファイルに配下のコンポーネントとルーティングのすべての情報を書いてきましたが、情報量が多くなり、だんだんと見づらくなってきました。

そこで最後に、ルーティングの定義部分だけを別ファイルへ切り出します。src/routerディレクトリを作成し、その直下にRouter.jsxファイルを作成します。

- root
    - src
        - router
            - Router.jsx
        - App.js
        - Detail.jsx
        - Home.jsx
        - Page1.jsx
        - Page2.jsx
        - index.js


次に、App.js内の<Routes>配下を丸ごと切り出し、router/Router.jsx内へ移動させます

Router.jsx

import { Route, Routes } from "react-router-dom";
import { Detail } from "../Detail";
import { Home } from "../Home";
import { NotFound } from "../NotFound";
import { Page1 } from "../Page1";
import { Page2 } from "../Page2";

export const Router = () => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/page1" element={<Page1 />}>
        <Route path="detail" element={<Detail />} />
      </Route>
      <Route path="/page2" element={<Page2 />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
};


App.js側では、切り出したRouter.jsxをインポートして、元の場所に配置します。

App.js

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

import { Router } from "./router/Router";

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      {/* 切り出したRouterをインポート */}
      <Router />
    </BrowserRouter>
  );
}

これで、責務分割ができている見やすいコンポーネントになりましたね。

まとめ

Reactキャッチアップの3週目は、React Routerを使ったルーティングの設定方法でした。

ちなみに、React Router 5系から6系へバージョンアップした際、大幅な変更があったようで、この記事で書いた内容は5系では動きませんので、注意してください。