この記事では、ライブラリ「React Router」を利用して、SPA(Single Page Application)でルーティングを行う方法を、サンプルを交えながら説明します。
この記事で、ルーティングの苦手意識が薄くなって頂けたら幸いです。
はじめに
Reactで作成したアプリは、初回アクセスでページ全体を表示し、以降のページ遷移をjavascriptで行うのが主流となっています。
これをSPA(シングル・ページ・アプリケーション)と言います。
SPAでのページ切り替えで利用するのが、ライブラリ「React Router」になります。
ルーティングとは、「ルーティングテーブル」よりクライアントから要求されたURLから、表示するコンポーネントを振り分ける仕組みになります。
ルーティング機能は、アプリの規模が大きくなればなる程、重要な機能になります。
React Router
今回は複数あるライブラリの中で、「react-router-dom」を使用して説明したいと思います。
コマンドプロンプトを起動し、プロジェクトのルートディレクトリに移動した後、下記コマンドを実行して下さい。
npm install react-router-dom
React Routerは、Create React Appに組み込まれていないので、利用するためインストールする必要があります。
react-router-domの公式HPは、下記になります。
ルーティングテーブルの定義
ルーティングを行うには、「URLとコンポーネントを紐づける定義」を行う必要があります。
これをルーティングテーブルと言います。
下記は、ルーティングテーブルのサンプルになります。
ファイル名は「Router.js」とします。 ※ファイル名は任意です。
import { createBrowserRouter } from "react-router-dom";
import {SampleA, SampleB, SampleC } from './SamplePage'
const Routes = createBrowserRouter([
{ path: '/', element:<SampleA/>},
{ path: '/sampleB', element:<SampleB/>},
{ path: '/sampleC', element:<SampleC/>},
]);
export default Routes;
今回のルーティング設定は、下記URLと対応するコンポーネントになります。
URL | 表示コンポーネント |
---|---|
http://localhost:3000/ | SampleA |
http://localhost:3000/sampleB/ | SampleB |
http://localhost:3000/sampleC/ | SampleC |
ルーティングテーブルの定義が終わったら、アプリに「ルーターの機能を付与」する必要があります。
ルーターの機能を付与するには、「RouterProvider」コンポーネントを使用します。
react-router-domよりインポートして下さい。
後は、先ほど作成したルーティングテーブルを読込み、「RouterProvider」の「router」にセットします。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { RouterProvider } from 'react-router-dom';
import Routes from './Router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<RouterProvider router={Routes} />
);
表示用のコンポーネントも簡単に作成します。
export const SampleA = () => {
return (
<div>SampleA</div>
)
}
export const SampleB= () => {
return (
<div>SampleB</div>
)
}
export const SampleC= () => {
return (
<div>SampleC</div>
)
}
一通り準備が完了しました。
画面より、ルーティングの動作を確認したいと思います。
URLを変更するだけで、対応したコンポーネントが表示しているのが分かります。
単一のページで、複数の画面を表現するのがSPAになります。
簡単なサンプルですが、ルーティング・SPAが何となくイメージできていただければありがたいです。
createBrowserRouter(routes, {opts})
先ほどルーティングテーブルを作成する際に使用した関数になります。
引数は「routes」と「{opts}」の2つを持っています。
これらの引数を確認していきます。
routes
オブジェクト「Route」の配列です。
URLとコンポーネントを紐づる設定を行います。
プロパティ | 説明 |
---|---|
action | ルート配下をサブミットした際に呼ばれる関数 |
caseSensitive | 大文字/小文字の区別 |
children | 配下のルート定義 |
element | 描画させるReact要素 |
errorElement | エラー発生時に描画するReact要素 |
handle | アプリ固有データ |
index | インデックスルートを示す「path:’/’を省略可能」 |
lazy | 遅延ロードの関数 |
loader | データロード中に描画する関数 |
path | リクエストパス |
{opts}
オプション設定になります。
オプション | 説明 |
---|---|
basename | アプリのベース名 |
future | v7で実装予定の機能を有効にするフラグ |
window | 主にテスト用。利用するwindowオブジェクト |
サンプル
createBrowserRouter(routes, {
basename: "/myapp",
});
routesをタグ形式で記述する
Routeをオブジェクト形式ではなく、タグ形式で記載できます。
子を持たせる場合に書きやすく、可読性が高い形式です。
import {
Route,
createBrowserRouter,
createRoutesFromElements
} from "react-router-dom";
import {SampleA, SampleB, SampleC } from './SamplePage'
const Routes = createBrowserRouter(
createRoutesFromElements(
<>
<Route index element={<SampleA />} />
<Route path='/sampleB' >
<Route path='/Sub' element={<SampleB />} />
</Route>
<Route path='/sampleC' element={<SampleC />} />
</>
)
);
export default Routes;
ルートパラメータ
「~/samleC/0120」のように、コンポーネントに「0120」等の値を渡す事が可能です。
設定方法は、pathに「/:変数」を設定し、コンポーネント側は「変数」を「useParams」を利用して取得します。
下記は、コード値を変数「code」とし、コンポーネント「SampleC」で受け取るサンプになりますす。
先ずはpathに「/sampleC/:code」と設定します。
import {
Route,
createBrowserRouter,
createRoutesFromElements
} from "react-router-dom";
import {SampleA, SampleB, SampleC } from './SamplePage'
const Routes = createBrowserRouter(
createRoutesFromElements(
<>
<Route index element={<SampleA />} />
<Route path='/sampleB' element={<SampleB />} />
<Route path='/sampleC/:code' element={<SampleC />} />
</>
)
);
export default Routes;
「react-router-dom」から「useParams」をインポートし、ルーティングテーブルで設定した「code」として受け取ります。
取得値を確認できるよう、画面に「code」を表示しています。
import { useParams } from "react-router-dom";
export const SampleA = () => {
return (
<div>SampleA</div>
);
}
export const SampleB= () => {
return (
<div>SampleB</div>
);
}
export const SampleC= () => {
const params = useParams();
return (
<div>SampleC - {params.code}</div>
);
}
ブラウザ開いて、URL「localhost:3000/SampleC/0120」にアクセスします。
URLに設定した「0120」が、コンポーネント側で受け取れている事が確認できました。
ルートパラメータの省略
ルートパラメータを設定しても、値が入らないケースがあり「404 Not Found」になる・・・
そんな時は、ルートパラメータの末尾に「?」を設定します。
これにより、ルートパラメータを省略してもエラーが発生する事はありません。
下記は、先ほどのサンプルのpathに「?」を付与しています。
import {
Route,
createBrowserRouter,
createRoutesFromElements
} from "react-router-dom";
import {SampleA, SampleB, SampleC } from './SamplePage'
const Routes = createBrowserRouter(
createRoutesFromElements(
<>
<Route index element={<SampleA />} />
<Route path='/sampleB' element={<SampleB />} />
<Route path='/sampleC/:code?' element={<SampleC />} />
</>
)
);
export default Routes;
codeの値が省略される事があるので、初期値を与えておきます。
※初期値に関しては任意です。設定しない場合「code=undefined」になります。
~~ 略 ~~
export const SampleC= () => {
const { code = '0120'} = useParams();
return (
<div>SampleC - {code}</div>
);
}
では、ブラウザでURL「localhost:3000/SampleC」を開いてみましょう。
URLに「0120」を省略しましたが「404 Not Found」が発生せず、コンポーネント側で分割代入時の初期値が表示されています。
「*」キャッチオールセグメント
ルートパラメータに「*」を付ける事で、可変長のパスを設定できます。
この設定を行う事で、パラメータの数をバラバラに送る事が可能になります。
下記は、サンプルのpathに「*」を付与しています。
import {
Route,
createBrowserRouter,
createRoutesFromElements
} from "react-router-dom";
import {SampleA, SampleB, SampleC } from './SamplePage'
const Routes = createBrowserRouter(
createRoutesFromElements(
<>
<Route index element={<SampleA />} />
<Route path='/sampleB' element={<SampleB />} />
<Route path='/sampleC/*' element={<SampleC />} />
</>
)
);
export default Routes;
パラメータの数が可変長のため、「’*’」で取得します。
~~ 略 ~~
export const SampleC= () => {
const { '*' : vlarg} = useParams();
return (
<div>SampleC - {vlarg}</div>
ブラウザでURL「localhost:3000/SampleC/0120/333/906」を開いてみましょう。
「SampleC/」以降のURLが全て取得出来ている事が分かります。
クエリパラメータの取得
先ほどはURLに情報を紐付けてコンポーネントに送りましたが、今回はクエリパラメータの取得方法になります。
クエリパラメータは、URLの末尾に付与する「?」以降の文字列を指します。
今回の修正はコンポーネントのみで、ルーティングテーブルの修正は不要です。
下記は、クエリパラメータを取得するサンプルになります。
クエリパラメータの取得には、「useSearchParams」をインポートする必要があります。
import { useSearchParams } from "react-router-dom";
~~ 略 ~~
export const SampleC= () => {
const [params] = useSearchParams({code:'0120'});
return (
<div>SampleC - {params.get('code')}</div>
)
}
ブラウザでURL「localhost:3000/SampleC?code=0120」を開いてみましょう。
クエリパラメータが取得出来ている事が分かります。
errorElement属性
errorElementにReact属性を設定する事で、ルーティング時に例外が発生した場合に設定したReact属性を表示します。
下記サンプルでは、「path=’/sampleB’」に「errorElement」を設定しています。
import {
Route,
createBrowserRouter,
createRoutesFromElements
} from "react-router-dom";
import {SampleA, SampleB, SampleC, ErrorPage } from './SamplePage'
const Routes = createBrowserRouter(
createRoutesFromElements(
<>
<Route index element={<SampleA />} />
<Route path='/sampleB' errorElement={<ErrorPage/>} element={<SampleB />} />
<Route path='/sampleC' element={<SampleC />} />
</>
)
);
export default Routes;
コンポーネント「SampleB」には、強制的に例外が発生する様に組み込んでいます。
また、例外時に表示するコンポーネント「ErrorPage」を追加しました。
ErrorPageは、「useRouteError」からエラーのメッセージを取得して画面に表示しています。
import { useSearchParams, useRouteError } from "react-router-dom";
~~ 略 ~~
export const SampleB= () => {
throw new SyntaxError('SyntaxError!!!');
}
~~ 略 ~~
export const ErrorPage = () => {
const err = useRouteError();
return (
<div style={{color:'#F0F'}}>ErrorPage {err.message}</div>
)
}
画面より、エラーの発生とエラーメッセージを確認します。
設定したエラーメッセージの確認ができました。
loader属性
loader属性は、コンポーネントで利用するデータを準備します。
loaderに設定できる関数は、下記条件が必要になります。
下記がサンプルになります。
先ずはルーティングテーブルの修正を行いました。
関数「Loader」を作成しています。
引数「{params}」は、ルートパラメータから「code」を取得しています。
また、戻り値は「Promise()」です。
作成した関数は、「loader属性」にセットしています。
import {
Route,
createBrowserRouter,
createRoutesFromElements
} from "react-router-dom";
import {SampleA, SampleB, SampleC, ErrorPage} from './SamplePage'
const Loader = ({params}) => {
console.log(params.code);
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolve');
resolve([
{code:'001', name:'りんご'},
{code:'002', name:'みかん'},
{code:'003', name:'すいか'},
{code:'004', name:'なし'}
]);
}, 5000);
}).then((res) => {
console.log('then');
return res;
});
};
const Routes = createBrowserRouter(
createRoutesFromElements(
<>
<Route index element={<SampleA />} />
<Route path='/sampleB' errorElement={<ErrorPage />} element={<SampleB />} />
<Route path='/sampleC/:code' loader={Loader} element={<SampleC />} />
</>
)
);
export default Routes;
SampleCは、「useLoaderData」より、loader属性が取得したデータを取得し、画面へ描画しています。
import { useSearchParams, useRouteError, useLoaderData } from "react-router-dom";
~~ 略 ~~
export const SampleC= () => {
const [params] = useSearchParams({code:'0120'});
const data = useLoaderData();
return (
<div>
SampleC - {params.get('code')}
{data.map((dt)=>(<div key={dt.code}>{dt.name}</div>))}
</div>
)
}
画面で確認したいと思います。
ブラウザで「localhost:3000/SampleC/0120」にアクセスします。
アクセスして5秒後に画面が表示しました。
また、関数「Loader」にて、ルートパラメータの「code値」が取得出来ている事が、開発者ツールより確認できます。
おわりに
長くなりましたが、今回はReact Routerについて解説致しました。
React Routerは、分かりにくい所が多くて沼にはまる感じです。
少しでも苦手なイメージが払しょくできれば良いなと思います。
自分も、この記事を書いてサンプル作成しているうちに、少し苦手な意識が薄れた気がします。