【React】React Router dom v6, v6.4

React

v6とv6.4以上でルートの作成方法が変わったので、メインで6.4のものを、後半にv6のものをメモしていきます。

v6.4以上のルート作成

createBrowserRouterを使う

React Router v6.4+ で導入された新しいルーティング API の一部。ルーティングをオブジェクトや配列として定義し、それを RouterProvider に渡す。ルート構成の定義がデータ駆動型。

オブジェクトとして定義する方法

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

const router = createBrowserRouter([
  {
    path: "/",
    element: <HomePage />,
  },
  {
    path: "/signin",
    element: <SignInPage />,
  },
]);

作ったルートを使うときRouteProviderコンポーネントを使う

function App() {
  return (
    <RouterProvider router={router} />
  );
}
階層的ルーティング

以下のようにオブジェクト内にchildrenプロパティでネストされたパス、コンポーネントを設定する。このときパスは相対的なものなので、下側のネストされたpathがservice1だけでも、親のパスがあるので、全体は/service/service1となっている。(/をつけると絶対パスになってしまう)

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      {
        path: "products",
        element: <Products />,
      },
    ],
  },
  {
    path: "/service",
    element: <Service />,
    children: [
      {
        path: "service1",
        element: <Service1 />,
      },
    ],
  },
]);

親コンポーネントには、どこに子コンポーネントが表示されるか、Outletコンポーネントを使って指定する。これがなければそのパスにアクセスしても子コンポーネントは表示されない

function Service() {
  return (
    <div>
      Service
      <Outlet />
    </div>
  );
}
404ページとエラーページ

errorElement:にコンポーネントを渡すことで、エラーが起きたときやページが見つからなかったときにそのコンポーネントを表示させることができます。(しかし404ページは専用に作った方がわかりやすいので最後にpath=”*”と設定して専用の404コンポーネントを表示した方が良い

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    errorElement: <ErrPage />, // エラー時に表示
    children: [
      {
        path: "products",
        element: <Products />,
      },
      {
        path: "*", // 404 ページを明示的に設定
        element: <NotFoundPage />,
      },
    ],
  },
]);
動的ルーティング

useParamsフックを使ってURLの変数部分を取得する

ルート作成 :変数名 でそのURLの値を取得できる。

{
    path: "/product/:id",
    element: <ProductElement />,
  },

useParamsを使う params.変数 でパスに設定しておいた変数の値が取得できます。

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

function productElement() {
  const params = useParams();
  return <div>id is {params.id}</div>;
}
export default productElement;
Route Guard

まだ難しくてよくわからないんですが、実装方法として2種類くらい見たのでメモ。
 基本的に認証したい場合って認証してからルートを決めたい場合が多いと思うんですけど、そうするとRouterの外でuseNavigateを使うことになってしまって、普通にコードを書こうとするとうまくいきません。Routerの中でどう検証作業とRedirectをさせるかがポイントになるのかな…と思います。

●ルート設定の中で検証

Routerの中で認証用のコンポーネントを使い、認証されていたら子コンポーネントを表示、そうでなければログイン画面などにNavigateするというやりかた。シンプルで使いやすい

{
      path: "/",
      element: <RequireAuth />,
      children: [
        {
          path: "",
          element: <Dashboard />,
        },
      ],
},

//RequireAuthの中で
return isAuthenticated ? <Outlet /> : <Navigate to="/login" />;

●コンポーネントの中でuseContextを使い検証してリダイレクト

useContextを使い、守りたいページのコンポーネントで認証されているかどうかを検証して、されていなければuseNavigateでリダイレクトさせる。これもシンプルですが、useContextを使う必要がある。

 const { user } = useAuth();
  const navigate = useNavigate();
  useEffect(() => {
    if (!user) {
      navigate("/");
    }
  }, [user]);

●ほかにもAuthRouterとか作っている方もいたけど、複雑そうだったのでまたの機会に

createRoutesFromElementsを使う場合

Routeタグなど設定したコンポーネントを使ってルートを設定する(複数ルートがある場合でも、RoutesではなくRouteで囲わなくてはならない。)

import { createBrowserRouter, createRoutesFromElements } from "react-router-dom";

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Route>
  )
);
createBrowserRouterでの階層的ルーティング

ヘッダーがあり、その下にネストされたコンポーネントが表示される場合

createBrowserRouterでネストされた構造のコンポーネントを設定。親コンポーネントはレイアウトコンポーネントのような扱いとなる。

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path="/" element={<RootLayout />}>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Route>
  )
);

親コンポーネントの中での設定

import { NavLink, Outlet } from "react-router-dom";

function RootLayout() {
  return (
    <div className="root-layout">
      <header>
        <nav>
          <h1>webSite</h1>
          <NavLink to="/">Home</NavLink>
          <NavLink to="about">About</NavLink>
        </nav>
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

mainタグの中にOutletコンポーネントをいれ、ここに子コンポーネントを表示するという設定を行っている。

さらにネストする場合

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route path="/" element={<RootLayout />}>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/help" element={<HelpLayout />}>
        <Route path="faq" element={<Faq />}/>
        <Route path="contact" element={<Contact />}/>
      </Route>
    </Route>
  )
);

同様にlayoutコンポーネントを親にして子コンポーネントを設定する。HelpLayoutコンポーネントの中でOutletで子コンポーネントを表示する位置も設定する。また、パスは/がつくとルートからの絶対パスになってしまうので、path=”faq”のように/をつけずに設定すると、ネストするときは勝手にパスが/help/faqのようになります。

v6のセットアップ

react-router-domの機能を使うために、使いたいコンポーネントすべてをBrowserRouterでラップする必要がある。

BrowserRouterを使う方法

HTML5 History API(ブラウザが提供するJavaScript APIの一部ブラウザが提供するJavaScript APIの一部)を使ってURLを管理し、適切なコンポーネントをレンダリングします。

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

function App() {
  return (
    <BrowserRouter>
      <h1>App</h1>
    </BrowserRouter>
  );
}

export default App;

アプリ全体を同じルーティングにするときはmain.jsxなどで使っても構いません

main.jsxで使う場合(BrowserRouterは重複できないので、main.jsxで使うならApp.jsxのは消しておく)

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

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>
);

他にもカスタムして高度な設定ができるRouterや、#以降の値で出しわけるHashRouterなどがありますが、1つのアプリ内で使えるRouterの種類は1種類で、複数は使えません。一番基本的なものがBrowserRouterですが、要件に合わせて1つ選んで使いましょう

宣言的ルーティング(declarative routing)

パスとコンポーネントを決めて、そのパスにアクセスすると設定したコンポーネントを表示する

<Route path=”” element={<component/>}/>…どのパスにどのコンポーネントを表示するか決める(v5では部分一致でもルーティングしてしまうので、完全一致の場合はexactをつける必要があったが、v6からはデフォルトで完全一致になったのでいらない。)

<Routes />…どのルートを表示するか、URLが一致するルートをこのタグから検索するので、1つしか<Route/>を使わない場合でも、<Routes/>で囲わないと機能しません。

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

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} exact />
    </Routes>
  );
}

これで、/のパスにアクセスするとHomeコンポーネントが表示されるようになります。

Not Found 404ページ

Not Found(404ページ)を表示するためには、Routesの中にpath=”*”(全て)としてコンポーネントを作成する必要があります。これで、<Routes/>の中でルートを検索して、特定のルートに当てはまらないもの全てのページに404ページを表示できます。

<Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      {/* デフォルトの NotFound コンポーネント */}
      <Route path="*" element={<NotFound />} />
</Routes>

リンクを作る

<Route />などでルートは設定しましたが、まだリンクを作っていないので手動でURLを打たないとページ遷移できません。そこで今度はリンクなどを作っていきます。

<Link to=”” />…リロードせずに画面遷移させるリンクを作る。

<Link to="/users">Go to Users Page</Link>

<NavLink to=”” />…ナビゲーションで使いやすいよう、現在のページで表示されていたら自動的にactiveクラスがつく

aタグはリロードが発生してしまい、ステートなどがリセットされてしまうためReactなどで使うには良くありません。LinkやNavLinkを使うようにしましょう

階層的ルーティング(nested routing)

ネストされたルーティングを設定する

サイト上部にナビのNavbarコンポーネントを置いておきたいとき、<Route/>タグで囲んで、それぞれのページをNavbarルートの子コンポーネントに置きます。

<Routes>
        <Route path="/" element={<Navbar />}>
          <Route exact path="/" element={<Home />} />
          <Route path="/about" element={<AboutUs />} />
          <Route path="/users" element={<Users />} />
        </Route>
</Routes>

これだけだとまだルートの子コンポーネントをレンダリングする設定になっていません。ルートの子コンポーネントを表示するには、Navbarの中に、どこに子コンポーネントのページを表示するか決め、その場所に<Outlet />というタグを挿入する必要があります。

//Navbar コンポーネント 
<>
      <nav>
        <Link to={"/"} className="logo-nav">
          Top
        </Link>
        <div className="link-cont">
          <Link to="/">Repos</Link>
          <Link to="/users">Users</Link>
        </div>
      </nav>
      <Outlet />
</>

上図はNavbarコンポーネント内の、ナビの部分の下に子コンポーネントが表示されるように<Outlet />が配置されています。

動的ルーティング(dynamic routing)

urlのパラメータを元にしてルートを生成する

例えば、以下のように、ユーザーの名前をパスにいれて、そのユーザーのプロフィールページを表示するルートを作成する。URLのパラメーターとして取得したい動的な部分を:コロン+変数名として(例 :username)パスの中に組み込む

<Route path="/users/user/:username" element={<UserProfile />} />

URLを元に表示したいコンポーネントの中に、useParams というフックをインポートし、それを使ってURLの変数部分の値を取得して、そのデータを使ってページに情報を表示することができる。

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

const UserProfile = () => {
  const { username } = useParams();
  return <h1>{username}</h1>   
};

Johnと入れればJohnという文字が表示され、Aliceと入れたらAliceという文字が表示される

const response = await axios.get(
        `https://api.github.com/users/${username}`
      );

<Link to={`/users/user/${user.login}`} className="view-btn">
              View User
            </Link>

例えば上のように名前を元にユーザーを探すAPIを叩いたりしてデータを表示したりできる。それぞれのページへのリンクも、APIで取ったデータから名前のプロパティを指定してパスを動的に設定できる

プログラムによる画面制御(programmatic navigation)

useNavigateなどのフックを使った画面遷移、ある値を元に条件で分岐させる、等ルーティングをコードを書いて制御できます。

useNavigate…コンポーネントなしでページ遷移できるフック

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

  const navigate = useNavigate();

  <button onClick={() => navigate(`/users/user/${user.login}`)} >
      View User
  </button>

イベントハンドラなどをトリガーにしてページ遷移できる、if文などの条件分岐でページ遷移が行えます

const navigate = useNavigate();
  const handleLogout = () => {
    navigate("/", { replace: false });
  };

replaceプロパティを付けることができ、Trueだと履歴として残らないので「戻る」ことができません。

Route Guard

条件やルールに従ってアクセスできるルートを出しわける機能。認証されてないアカウントから特定のルートにアクセスできないよう守ったりできる

Navigate…ルートを守ったり、条件が変わったらリダイレクトしたりできる
使い方:以下のようにログインしていればプロフィールコンポーネントを表示し、そうでなければNavigateコンポーネントがトリガーされる。Navigateコンポーネントの中で認証がなければどこへリダイレクトされるか設定しておく

<Route
            path="/authProfile"
            element={
              isLogged ? (
                <AuthProfile username={username} />
              ) : (
                <Navigate replace to={"/login"} />
              )
            }
/>

※ページが丸々変わる場合はこっちの方がURLに矛盾が出ないのでいいです。ログインコンポーネントをモーダルウィンドウのように一部で表示する場合はこのようなリダイレクトは使いません。