Reactで複数のページを作る方法 – Router v6.0準拠

Reactは、SPA(Single Page Application)に最適化されていますが、Reactでも “React Router”ライブラリーを使うことで、下記のようなApp.jsの内部に複数のページ(path)を持つWebサイトを作ることができます。Routerは2021年の10月頃にver 6.0.0の正式版がリリースされ、それ以前のver 5とは大きく変わりました。本稿の記載内容は全てver6.0に準拠しています。

Router

react-router-domのインストール

npmコマンドを使ってreact-router-domをインストールします。バージョンを指定する場合は下段のようにします。

npm install react-router-dom
npm install raect-router-dom@6

React Routerを使用する場所は、通常は最上位のコンポーネントのApp.jsですので、続いてApp.jsにてReact Routerライブラリーをインポートします。Reactそのものとcssのインストールの記載は省略しています。v6からは<switch>の代わりに<Routes>を使います。App.jsで<Link>を使うことはあまりないと思いますので、Linkのインポートは多くの場合App.jsでは不要です。

App.js

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

BrowserRouterとは

BrowserRouterではHTMLのhistory APIを用いて、ユーザー側の表示とURLを一致させます。他方HashRouterはHTMLのハッシュ#を用いてルーティング処理をし、URLのパスにも#が混じります(両者の詳細についてはstackoverflowで議論を参照)。下記のようにBrowserRouterをas RouterとしてインポートするのはHashRouterへの切り替えを容易にするためです。ただ多くの場合はhistory APIを用いますので、そのままインポートしても問題ありません(公式ドキュメント及び本稿のトップ画像の例)。

React Routerのコード本体(Router)

App.jsの関数部分は、次のように記載します。<Router>と内側の<Routes>で全体を包んだ後、個別のページを記載したコンポーネントをelement={ }の波括弧の内側に記載します。この例では、<Home/>、<About/>、<Products/>の3つのコンポーネントがあります。<Route>のpath属性にはそれぞれのページのpath(URL)を記載します。

ここでは<Home/>コンポーネントにrootの”/”を割り当て、<About/>コンポーネントに”/about”を、<Products/>コンポーネントに”/products”のパスを割り当てています。3つのコンポーネントは別ファイルにて、Home.js、About.js、Products.jsとして設け、ここでは記載を省略していますが、それぞれをApp.jsの上段でインポートします(通常のReactの構成)。

App.js

function App() {
return (
< div className="app">
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About/>} />
<Route path="/products" element={<Products/>} />
<Route path="/*" element={<NotFound/>} />
</Routes>
</Router>
</ div>
);
}

カスタム404エラーページの追加方法

上記に記載の4つ目にコンポーネントは、カスタムの404エラーページです。オールキャッチのパス、”/*”を割り当てることで、存在しないパスにアクセスがあった場合は<NotFound/>コンポーネントが表示されます。

Routeに複数のコンポーネントを持たせる方法

始めから複数ページを持たせる予定であれば、上記のような構成にすると思いますが、場合によってはSPAとしてApp.js上に複数のコンポーネントを直接設置していたところに、後からページを追加したいということがあると思います。その場合は、新たにルートパス”/”を割り当てる中間の<Home/>等のコンポーネントを作らずとも、下記のように、Routeコンポーネントに直接複数のコンポーネント(ここでは<Header/>と<Main/>)を持たせることができます。複数のコンポーネントを空のタグで括っている点がポイントです。

App.js

function App() {
return (
< div className="app">
<Router>
<Routes>
<Route path="/" element={<
<>
<Header/>
<Main/>
</>
}/>
<Route path="/about" element={<About/>} />
<Route path="/products" element={<Products/>} />
<Route path="/*" element={<NotFound/>} />
</Routes>
</Router>
</ div>
);
}

ページ毎に共通のNavbarやFooterを設定する方法

これまでの方法に従うと、NavbarやFooterはどのページにもインポートが必要になります。<Router>と<Routes>の間には、<div>タグや他のコンポーネントが入っても問題なく動きますので、上部の<Router>と<Routes>の間にNavbarコンポーネントを、下段の<Router>と<Routes>の間にFooterコンポーネントを挟むことで、どのページでも上段と下段にそれぞれNavbarとFooterを表示させることもできます。この場合、これまで説明した例では<Router>の外側にあったdivタグを<Router>のすぐ内側に配置し、<Routes>のすぐ外側を<div>タグあるいは<main>タグで括ることで、Router内側の<div>タグの直下の子要素に、<Navbar/><main></main><Footer/>を3つ並べることができます(スタイリングがしやすい)。なお、<Routes>と<Route>の間には<div>タグ他を配置できません。下記は、以上を適用した事例です。クラス名は全てTailwindです(本稿とは関係ないのであまり気にしないでください)。



exact属性の役割

こちらのexact記載はv5の記載ですので、v6を使われている方は飛ばし、次のLinkに飛んでください。v6では前方部分一致によるレンダリングがなくなったのでexact不要になりました。

<Route>の内部で、exactという属性(props)を渡しています。exactを指定しないとURLとpathの前方の部分一致でレンダリングしますが、exactを渡すことで、URLと完全に一致するpathの内容をレンダリングをします。

具体的にこの例でexactを指定しない場合、”/about”と”/products”のURLをリクエストした場合でも、それぞれのURLがrootのpathである”/”と前方が部分一致していることから<Home/>コンポーネントが表示され、<About/>コンポーネントと<Products/>コンポーネントは返ってきません。

ただし正確には、<Switch>は指定されたURLを上から順にソートするので、Homeコンポーネントの部分を3番目に配置することで、この例ではexactなしでも動きます(下記)。公式ドキュメントの一番初めに記載された事例はこの方法です。また、exactを全てに記述せずともrootのpathにだけを渡せば、この例では動きます。

リンクを張る方法(Link)

<Routes>の内部で固有のpathを持つ3つのコンポーネントを設置しましたので、続いて互いのコンポーネント間で内部リンクを張ります。内部リンクはもちろん<a>タグでも張れるのですが、React Routerに備わる<Link>を使用する方がずっと高速です。<a>タグがリフレッシュを伴って、サーバから別のページを読み込むのに対して、<Link>はクライアント側でページ遷移の処理をします。また<a>タグでは、UseStateやRecoilで保持される状態もリフレッシュに伴い失われますが、<Link>では失われません。

公式ドキュメント及び多くのReact Routerを紹介するブログでは、<Link>を含む<nav>タグを、App.jsで<Routes>の上部に並列に記載していてわかりにくいのですが、<Link>は実質的にはただの<a>タグですので、どこにでも設置できます。

下記コードはサブコンポーネントのHome.jsにリンクを設置する例です。2行目でReract-router-domからLinkをインポートし、Home関数の内部で<Link>タグを使用して内部リンクを張っています。


Home.js

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

function Home() {
return (
< div className="home">
<Link to="/products">Link to Products</Link>
<Link to="/about">Link to About</Link>
</ div>
);
}

export default Home;

応用編 – パラメーターの設定

クエリパラメーターの設定方法

React RouterのLinkを使うと、リンクにクエリパラメーターを持たせることも容易です。下記の例では、dataのページに対しするリクエストで、変数名queryに変数paramというクエリパラメーターを持たせた例です。pathnameにはパスを、searchにはクエリパラメーターを指定して、全体をオブジェクトとしてtoに渡します。ULRにすると、/data?query=paramとなります。

<Link
to={{
pathname: "/data",
search: "?query=param",
}}
>

パスパラメーターの設定方法

パスパラメーターを設定する場合は、それぞれ固有の内容を持つので、Linkではなく<Route>に設定します。具体的にはRouteのpathの部分にコロンを付けてパスパラメーターの変数名を付します。下記のはpostというパスにidという変数名でパスパラメーターを付して、Postコンポーネントを読み込む例です。実際のURLの例は例えば/post/10(idが10の場合)や/post/james(idがjamesの場合)のようになります。

<Route path="/post/:id" element={<Post/>} />

パスパラメーターを読む方法 – UseParams

パスパラメーターは通常バックエンドで処理するので、フロントエンドのReactで読むことはあまりないかもしれませんが、react-router-domのuseParams関数を使うと処理が可能です。useParamsで取得した内容を変数に格納し(下記の例ではparams)、具体的なパスパラメーターはparams.変数名(下記の例ではparams.id)で得ることが可能です。本事例では、パスパラメーターの内容を表示させているだけですが、実際はここでパスパラメーターを使って、バックエンドや外部APIにリクエストをすることが多いと思います。

Post.js

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

function Post() {
const params = useParams();
return (
< div>
Post {params.id}
</ div>
);
}

export default Post;

応用編 – リダイレクトの方法

Navigate, useNavigate

React Routerではリダイレクトの設定も容易です。リダイレクトは、例えばバックエンドあるいは外部APIへのリクエストに対して404エラーを得たら404ページにリダイレクトさせる場合や、Postリクエスト等なんらかの処理を終えた後に、他のページにユーザーを戻すときなどで使えます。下記はHTTPステータスコードを処理する例です。実際はcatchされるerrorの内容を読むことになりますが、ここでは単に変数statusにコードを格納しています。if文でステータスコードが404のときに、/notfoundページへリダイレクトしています。HTTPステータスコードが404でないときは、JSXの内容(本件ではPostという文字が表示されるだけ)が表示されます。

Post.js

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

function Post() {
const status = 404;

if (status === 404) {
return <Navigate to="/notfound" />;
}

return (
< div>
Post
</ div>
);
}

export default Post;

次の例はなんらかの処理の後にリダイレクトする例です。ボタンに対するonClickで処理(本件ではconsole.log)とリダイレクトが生じます。リダイレクトには、useNavigate関数からの戻り値を格納した変数(ここではnavigate)を使用します。ただuseNavigateはコンポーネントの内部でしか使えないので、Context等コンポーネントの外でリダイレクトを定める場合はwindow.location(“/about”)を使うとリダイレクトできます(リフレッシュは生じる)。

Post.js

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

function Post() {
const navigate = useNavigate();
const onClick = () => {
console.log("Executed");
navigate("/about");
};

return (
< div>
<h1>Post</h1>
<button onClick={onClick}>CLick</button>
</ div>
);
}

export default Post;

本稿ではReactで複数のページを作る方法をご紹介しました。Router ver6.0には、他にも入れ子構造に対応しているなどの特徴があります。ここまで読んで頂いた方は入れ子構造も容易に理解できると思います。詳細は公式ドキュメントをご覧ください。

応用編 – useLocation

useLocationを使用すると、現在表示されているページのパスを取得できます。前述の例では、”/about”のページにいるときは”/about”が取得でき、”/products”のページにいるときは”/products”が取得できます。この機能を使うと、ヘッダー、フッター、Navbarなど各ページに共通するコンポーネント内の要素(例:色)を、ページ毎に動的に変化させることができます。Javascriptには現在のルートパスを取得するlocationというクラスが元々備わっていますが、Reactのコンポーネント内ではlocationをそのまま使えないので、react-router-domのuseLocationを使用します。例えばNavbarコンポーネントがあるとして、次のような使い方ができます。

Navbar.js

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

function Navbar() {
const location = useLocation();

const isRoute = (route) => {
if (route == location.pathname) {
return true;
};

//中略
}

export default Navbar;

isRoute関数は、引数として渡されたパスが、現在のページのパスと等しければ、trueを返します。よって、例えばページ毎にクラス名を変更したい場合は、次のような三項演算子をHTMLタグの内部で組みます。

//"/about"のページに対して動的に変化させたいタグのクラス
className={
isRoute("/about")
? "navbarActive"
: "navbarDefault"
}

//"/products"のページに対して動的にさせたいタグのクラス
className={
isRoute("/products")
? "navbarActive"
: "navbarDefault"
}

React Router ver6.0が学べる講座

本稿を読んで頂ければ、React Router ver6.0は問題なくお使い頂けると思いますが、動画の講座で学びたい方はUdemyのこちらの講座「React Front To Back 2022」をお勧めいたします(2022年5月現在)。この講座は本稿と同様に、元々はReact Router ver5.0を用いて解説していましたが、ver6.0へのアップグレード後すぐに動画を更新・追加していました。Router以外にも、Reactの基本からfetchを使ったリクエスト、JSON server、Tailwind、React Context、Redux、Firebase、Heroku、MERNスタックなど、基本から応用、フロントエンドからバックエンドまで2022年現在では最新で魅力的なReactのテクノロジーを学ぶことができます(※但し、私はReact ContextとReduxについてはRecoilを選ぶ点については私のこちらの記事を参照)。講義は英語(米国)ですが、講師のBradさん非常にゆっくりと話しますので聞きとりやすいと思います。価格は12,000円で受講期間の制限なく、何十万円もするようなプログラミングスクールよりずっと安い上、Udemyは時々セールをします。先ほどのリンクをクリックし定価12,000円が表示されても、お急ぎでない場合は、1-3週間置いて改めてクリック(下記画像のクリックでも可)してみてください。


React Routerを日本語で学べる講座

英語で学ぶのが辛い方は、Udemyのこちらの講座「Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版」をお勧めいたします。動画でのRouterのバージョンを5.0から6.0に更新していない点は非常に残念なのですが(2022年5月現在)、講師の岡田さんの説明はわかりやすいです。Reactの応用編という位置付けで、useMemo、useCallbackなどのレンダリング最適化、グローバル状態管理ではContextとRecoil、カスタムフック、ChakuraUI、それにTypeScriptの入門まで大変分かりやすく説明されております。


今日も最後まで読んで頂きありがとうございました。