react-router-domのRouteコンポーネントをネストしてコンポーネントを切り替える

2021-02-20 React react-router-dome

はじめに

  • react-router-domのRouteコンポーネントをネストしてコンポーネントを切り替える方法について書きます

  • react-router-domの基本的な使い方についても書きます

環境

  • macOS BigSur (11.2.1)

  • react (17.0.1)

  • react-router-dom (5.2.0)

react-router-domとは

  • SPAの画面遷移(コンポーネント切り替え)を実現してくれるモジュール

  • SPAは1つのhtml上でURLに一致する要素をJSにより切り替えて実現している

  • このエミュレートはreact-router-domがHistoryAPIを利用して実現している

RouteコンポーネントをネストしてURLにマッチングさせる実装

ソースコードは公式から拝借

import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
  useParams,
  useRouteMatch
} from "react-router-dom";

// Since routes are regular React components, they
// may be rendered anywhere in the app, including in
// child elements.
//
// This helps when it's time to code-split your app
// into multiple bundles because code-splitting a
// React Router app is the same as code-splitting
// any other React app.

export default function NestingExample() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/topics">Topics</Link>
          </li>
        </ul>

        <hr />

        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          {/* ポイント1 */}
          <Route path="/topics">
            <Topics />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
    </div>
  );
}

function Topics() {
  // The `path` lets us build <Route> paths that are
  // relative to the parent route, while the `url` lets
  // us build relative links.
  // ポイント2
  let { path, url } = useRouteMatch();

  return (
    <div>
      <h2>Topics</h2>
      <ul>
        <li>
          <Link to={`${url}/rendering`}>Rendering with React</Link>
        </li>
        <li>
          <Link to={`${url}/components`}>Components</Link>
        </li>
        <li>
          <Link to={`${url}/props-v-state`}>Props v. State</Link>
        </li>
      </ul>

      <Switch>
        <Route exact path={path}>
          <h3>Please select a topic.</h3>
        </Route>
        {/* ポイント3 */}
        <Route path={`${path}/:topicId`}>
          <Topic />
        </Route>
      </Switch>
    </div>
  );
}

function Topic() {
  // The <Route> that rendered this component has a
  // path of `/topics/:topicId`. The `:topicId` portion
  // of the URL indicates a placeholder that we can
  // get from `useParams()`.
  let { topicId } = useParams();

  return (
    <div>
      <h3>{topicId}</h3>
    </div>
  );
}
  • ポイント1

    • exactをtrueにして厳密に一致させないことで親ディレクトリに一致した場合Routeコンポーネントがレンダリングされる

    • exactを使用して厳密に一致させてしまうとサブディレクトリで後述するPlaceHolderを使用したマッチができない

  • ポイント2

    • useRouteMatchフックからRouteコンポーネントがURLマッチ時に使用したurlやpathを取得する

    • ネストしたRouteコンポーネントのURLマッチにpathを使用できる

  • ポイント3

    • :topicIdというPlaceHolderを使用することでディレクトリの階層が同じであれば何でもURLマッチさせられる

    • :topicIdはRouteコンポーネントのparamsプロパティに格納される、useParamsフックを使用して取り出せる