【React】React基礎編(ルーティング編)

この記事では、ライブラリ「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アプリのベース名
futurev7で実装予定の機能を有効にするフラグ
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」を開いてみましょう。

クエリパラメータが取得出来ている事が分かります。

【ルートパラメータとクエリパラメータの使い分けについて】

おおよそ下記使い分けの認識で良いと思います。

ルートパラメータ:idやcode等、一意のリソースを取得するのに用いる
クエリパラメータ:検索やフィルタリング等、付加情報を取得するのに用いる

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に設定できる関数は、下記条件が必要になります。

  • 引数としてリクエスト情報を受け取る事
  • 戻り値として取得したデータ、またはPromiseを返す事

下記がサンプルになります。

先ずはルーティングテーブルの修正を行いました。

関数「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は、分かりにくい所が多くて沼にはまる感じです。
少しでも苦手なイメージが払しょくできれば良いなと思います。

自分も、この記事を書いてサンプル作成しているうちに、少し苦手な意識が薄れた気がします。