4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React Router v6】Route をネストしたときの動きを確認する【Next.js から移植】

Last updated at Posted at 2021-11-14

本記事では分かりやすさを重視するため、F1 を題材として取り扱っています。

はじめに

Next.js のルーティングについて調べていたら、どうやら React Router がバージョンアップしてるらしいぞという情報を得たので触ってみました。

せっかくなので Next.js で作ったアプリを React に移植する形で進めていこうと思います。

React プロジェクトを作成

create-react-appコマンドを使ってプロジェクトを作成します。

yarn create react-app test-react-routing

今回は React でルーティングする際にマストで必要となるreact-router-domの他に、cracoprettierの計 3 つのパッケージをプロジェクトに追加しています。

yarn add craco prettier react-router-dom

準備

まず Next.js の機能を参照しているコンポーネントを純粋な React のものに置き換えていきます。
基本的にコンポーネントは切り出して別コンポーネントとして作成していたので、あまり変更箇所は多くないです。

useParams.js

useRouteruseLocationに置き換えていきます。

useParams.js
-import { useRouter } from "next/router";
+import { useLocation } from "react-router-dom";

const useParams = () => {
-  const location = useLocation();
+  const location = useLocation();

  return {
-    teamId: location.state?.teamId ?? null,
-    driverId: location.state?.driverId ?? null,
+    teamId: location.state?.teamId ?? null,
+    driverId: location.state?.driverId ?? null,
  };
};

export { useParams };

Image.jsx

next/imageImageコンポーネントを純粋なimgタグに置き換えていきます。

Image.jsx
-import NextImage from "next/image";

const Image = ({ alt, children, ...props }) => (
-  <NextImage alt={alt} {...props}>
+  <img alt={alt} {...props}>
    {children}
-  </NextImage>
+  </img>
);

export default Image;

Link.jsx

next/linkLinkコンポーネントをreact-router-domLinkコンポーネントに置き換えていきます。

Link.jsx
-import NextLink from "next/link";
+import { Link as ReactLink } from "react-router-dom";

-const Link = ({ children, ...props }) => <NextLink {...props}>{children}</NextLink>;
+const Link = ({ children, ...props }) => <ReactLink {...props}>{children}</ReactLink>;

export default Link;

Nav.jsx

あとは呼び出し箇所の変更を行います。
useParams.jsImage.jsxの使い方は特に変わりませんが、Link.jsxは Next.js と React Router で少し異なります。

  • Next.js の場合
    hrefにクエリ文字列を含む遷移先の URL、asに表示上の URL を設定する。
  • React Router の場合
    toに遷移先の URL、stateにオブジェクト形式でパラメータを設定する。

といった具合です。別に大したことではないんですが。

Nav.jsx
-const asUrl = `/${team.teamName}/${driver.driverName}`.replace(/\s/g, "-");
-const url = `/[team]/[driver]?teamId=${team.teamId}&driverId=${driver.driverId}`;
+const url = `/${team.teamName}/${driver.driverName}`.replace(/\s/g, "-");
+const state = { teamId: team.teamId, driverId: driver.driverId };

return (
  <li key={url}>
-    <Link href={url} as={asUrl}>
+    <Link to={url} state={state}>
      {driver.driverName}
    </Link>
  </li>
);

あとはアプリケーション全体を Rouer で囲ってしまえば準備完了で
す。

index.js
ReactDOM.render(
  <React.StrictMode>
+    <BrowserRouter>
      <App />
+    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

とりあえずここまでやれば React としては問題なく動くようになると思います。

いざルーティング

と行きたいところだったんですが、何もしていないのに想定通り動いてしまっています。
これはある意味想定外です。

なぜか

理由としては簡単で、ルーティング云々とは全く無関係に「あれば表示する」という作り方をしていたからでした。

Main.jsx
<main>
  {team && <Team {...{ team }} />}
  {driver && (
    <>
      <hr />
      <Driver {...{ driver }} />
    </>
  )}
</main>

ルーティングの実装

気を取り直してルーティング処理を実装していきたいと思います。
React Router v6 からネストされたルートの記述がシンプルになっているということで、素直に実装していきます。

以下のようになるんじゃないかと思います。

Main.jsx
<main>
  <Routes>
    <Route path=":team" element={<Team {...{ team }} />}>
      <Route
        path=":driver"
        element={
          <>
            <hr />
            <Driver {...{ driver }} />
          </>
        }
      />
    </Route>
  </Routes>
</main>

「よしこれで完成」と思いきや、あと一つ実装しなければいけないものがありました。

Outlet とかいうヤツ

なんか新しいコンポーネントが追加されてるな、位にしか考えていなかったんですが、これが非常に重要でした。

Outlet が設定されていない場合、最初にマッチしたパスの要素しかレンダリングしてくれません。
今回の場合、

  • :team
  • :team/:driver

という 2 パターンの URL にマッチさせたいにも関わらず、:teamにマッチした時点で以降のパスはどうやら無視されてしまうようです。

以下のようにチームのみ画面に表示されています

Outlet を使用することでこれを回避することができます。
公式ドキュメントには以下のように記されています。

An <Outlet> should be used in parent route elements to render their child route elements. This allows nested UI to show up when child routes are rendered. If the parent route matched exactly, it will render a child index route or nothing if there is no index route.

翻訳すると以下のようになります。

<Outlet>は、子のルート要素をレンダリングするために、親のルート要素で使用する必要があります。これにより、子ルートがレンダリングされたときにネストされたUIが表示されるようになります。親ルートが正確にマッチした場合は子のインデックスルートをレンダリングし、インデックスルートがない場合は何もレンダリングしません。

powered by DeepL

Outlet を実装する

何も難しいことはありませんでした。
以下のように、親となる要素に対して<Outlet />と 1 行追加するだけです。

Team.jsx
+import { Outlet } from "react-router-dom";
import Image from "@/components/Image";
import chiefImage from "@/assets/images/chief.png";

const Team = ({ team }) => (
  <>
    <h1>{team.teamFullName}</h1>
    <h2>{team.teamChief}</h2>
    <Image src={chiefImage} alt="chief" />
+    <Outlet />
  </>
);

export default Team;

これで本当の意味で想定通りに動いている、といった状態になりました。

ソースコードは以下にあります。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?