1
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?

【React Router v5→v6比較あり】ReactでSPAルーティングを実装してみた

1
Posted at

1. はじめに

今回はUdemyの講座でReact Routerを使ったルーティングを学びました。
学習で書いたコードはv5系のものですが、現在主流のv6との違いも合わせて整理しています。
SPAにおけるページ遷移の基本的な仕組みを理解することが今回のメインテーマです。

この記事のコードはReact Router v5系をベースにしています。
v6との違いは各セクションの末尾に「v6との違い」として補足しています。

2. アプリの全体構成

今回は複数ページを持つSPAを構築しました。
ファイル構成と全体のルーター設定から順番に確認していきます。

2.1 App.jsx

まず、アプリ全体のエントリーポイントとなるApp.jsxです。

App.jsx全体:

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

function App() {
  return (
    // BrowserRouterでアプリ全体を囲む
    <BrowserRouter>
      <div className="App">
        {/* グローバルナビゲーション */}
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      {/* ルーター設定を別ファイルに分離 */}
      <Router />
    </BrowserRouter>
  );
}

export default App;

BrowserRouterでアプリ全体を囲むのがルーティングの起点のようです。
ナビゲーションには通常の<a>タグではなく、Linkコンポーネントを使います。
Linkはページリロードなしで画面を切り替えてくれるため、SPAとして動作するのだと理解しました。

2.2 ルーター設定(Router.jsx)

講座ではルーター設定をRouter.jsxに分離していました。
以下はその構成イメージです(v5の書き方):

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

export const Router = () => {
  return (
    // Switchで囲むことで、上から順に最初にマッチしたRouteだけ表示する
    <Switch>
      <Route exact path="/" component={Home} />
      <Route path="/page1">
        {/* ネストしたRouteもSwitchで囲む */}
        <Switch>
          <Route exact path="/page1" component={Page1} />
          <Route path="/page1/detailA" component={Page1DetailA} />
          <Route path="/page1/detailB" component={Page1DetailB} />
        </Switch>
      </Route>
      <Route path="/page2">
        <Switch>
          <Route exact path="/page2" component={Page2} />
          {/* :idの部分がURLパラメーター */}
          <Route path="/page2/:id" component={UrlParameter} />
        </Switch>
      </Route>
      {/* pathなしはどのURLにもマッチする→404ページに使う */}
      <Route component={Page404} />
    </Switch>
  );
};

v5ではSwitchRouteの組み合わせでルーティングを定義するみたいです。
exactを付けないと/page1のルートが/page1/detailAにもマッチしてしまうため、完全一致させたい場合はexactが必要になるようです。

v6との違い:Switch・Route・exact

v6ではSwitchが廃止されてRoutesに変わり、exactも不要になったようです。
またRouteのコンポーネント指定方法がcomponent={Home}ではなくelement={<Home />}に統一されたと理解しました。

// v6の書き方
import { Routes, Route } from "react-router-dom";

export const Router = () => {
  return (
    // Routesに変更(exactは不要)
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/page1" element={<Page1 />} />
      <Route path="/page1/detailA" element={<Page1DetailA />} />
      <Route path="/page1/detailB" element={<Page1DetailB />} />
      <Route path="/page2" element={<Page2 />} />
      {/* URLパラメーター */}
      <Route path="/page2/:id" element={<UrlParameter />} />
      {/* 404ページ */}
      <Route path="*" element={<Page404 />} />
    </Routes>
  );
};

v6のRoutesは最も一致度の高いルートを自動で選んでくれるため、順番を気にしなくてよくなったようです。

3. useHistory・useNavigate(プログラム的なページ遷移)

ボタンクリックなどで画面遷移するには、Linkではなくhooksを使うみたいです。

Page1.jsx全体:

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

export const Page1 = () => {
  const arr = [...Array(100).keys()];

  // useHistoryでhistoryオブジェクトを取得する
  const history = useHistory();
  console.log(history);

  // pushメソッドでページ遷移する
  const onClickDetailA = () => history.push("/page1/detailA");

  return (
    <div>
      <h1>Page1ページです</h1>
      {/* stateとしてarrを渡す */}
      <Link to={{ pathname: "/page1/detailA", state: arr }}>DetailA</Link>
      <br />
      <Link to="/page1/detailB">DetailB</Link>
      <br />
      <button onClick={onClickDetailA}>DetailA</button>
    </div>
  );
};

useHistoryでhistoryオブジェクトを取得して、history.push()でページ遷移します。
またLinkto={{ pathname: "...", state: arr }}と書くことで、遷移先のページにデータを渡せるみたいです。

Page1DetailA.jsx全体:

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

export const Page1DetailA = () => {
  // useLocationでlocationオブジェクトを取得し、stateを分割代入
  const { state } = useLocation();
  console.log(state);

  const history = useHistory();

  // goBackで1つ前のページに戻る
  const onClickBack = () => history.goBack();

  return (
    <div>
      <h1>Page1DetailAページです</h1>
      <button onClick={onClickBack}>戻る</button>
    </div>
  );
};

渡されたstateはuseLocationstateで受け取れるみたいです。
前の画面に戻るにはhistory.goBack()を使います。

v6との違い:useHistory → useNavigate

v6ではuseHistoryが廃止されてuseNavigateに変わっています。

// v5
import { useHistory } from "react-router-dom";
const history = useHistory();

// pushで遷移
history.push("/page1/detailA");
// 戻る
history.goBack();
// v6
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();

// そのまま遷移先を渡す
navigate("/page1/detailA");
// 数値で履歴を移動する(-1が1つ前)
navigate(-1);

v6ではひとつのnavigate関数がすべてのナビゲーションを担うシンプルな設計になったのようです。

またLinkのstateの書き方もv6で変わっています。

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

// v6(stateを独立したpropに分離)
<Link to="/page1/detailA" state={arr}>DetailA</Link>

v6でもstateの受け取り方(useLocationstate)は変わらないみたいです。

4. URLパラメーター・クエリパラメーター

URLからパラメーターを取得する方法を学びました。

Page2.jsx全体:

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

export const Page2 = () => {
  return (
    <div>
      <h1>Page2ページです</h1>
      {/* URLパラメーター付きのLink */}
      <Link to="/page2/999">URL Parameter</Link>
      <br />
      {/* クエリパラメーター付きのLink */}
      <Link to="/page2/999?name=hogehoge">Query Parameter</Link>
    </div>
  );
};

UrlParameter.jsx全体:

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

export const UrlParameter = () => {
  // useParamsでURLパラメーターを取得する
  const { id } = useParams();

  // useLocationでsearchを取得する
  const { search } = useLocation();

  // URLSearchParamsでクエリ文字列をパースする
  const query = new URLSearchParams(search);

  return (
    <div>
      <h1>UrlParameterページです</h1>
      <p>パラメーターは{id}です</p>
      <p>クエリパラメーターは{query.get("name")}です</p>
    </div>
  );
};

useParamsはパス(/page2/:id)の:id部分を取得できます。
クエリパラメーター(?name=hogehoge)はJavaScript標準のURLSearchParamsを使ってパースするみたいです。
useLocationで取れるsearch?name=hogehogeの文字列で、そこにURLSearchParamsを渡して.get("name")で値を取り出せます。

useParamsuseLocationの使い方はv5・v6でほぼ変わらないようです。

5. 404ページ

どのルートにも一致しないURLへのフォールバック処理です。

Page404.jsx全体:

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

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

v5ではpathなしのRouteを最後に置くと、どのURLにもマッチしなかった場合のフォールバックになります。
v6ではpath="*"を使うように変わっています。

// v5
<Route component={Page404} />

// v6
<Route path="*" element={<Page404 />} />

まとめ

今回はReact Router v5を使ったSPAのルーティングを学び、v6との主な違いも合わせて整理しました。
v5→v6でAPIの書き方がシンプルに刷新されているため、両方を比較しながら学ぶのが理解しやすいと感じました。

今回の気づき

一番大きな気づきは、v6でexactが不要になり、SwitchRoutesに変わった点です。
v5では順番やexactの有無でバグが生まれやすかったのが、v6では最も一致度の高いルートを自動選択してくれるためシンプルになったのようです。
またuseHistoryuseNavigateに統一されたことで、1つの関数でpush・goBack・goForwardがすべて賄えるのも覚えやすいと理解しました。

ハマりやすいポイント

  • v5でexactを付け忘れると/page1/detailAにアクセスしても/page1のコンポーネントが表示されてしまう
  • Linkのstateの書き方がv5とv6で異なり(オブジェクト内 vs 独立prop)、バージョンを混同しやすい
  • クエリパラメーターはuseParamsでは取れないためuseLocationsearchURLSearchParamsを使う必要がある
1
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
1
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?