Reactキャッチアップへの道③ 『ルーティング』
こんにちは! スマレジ テックファームのMichiです!
前週に引き続き、Reactのキャッチアップをしていきます。今回は、『Reactのルーティング』について学びます。
はじめに
Reactでルーティングを設定するにあたって、『React Router』というライブラリを使用します。
下記コマンドで、ライブラリをインストールしてください。
npm install --save react-router-dom
※本記事ではReact Routerの最新バージョンである6系で解説を進めます。
また、ディレクトリ構造は以下のようになります。
- root
- src
- App.js
- Home.jsx
- Page1.jsx
- Page2.jsx
- index.js
それでは、はじめていきましょう。
基本的なルーティング
React Routerを使用するには、<BrowserRouter>を使います。<BrowserRouter>で囲った範囲でのみルーティングが有効になるので、ルートコンポーネントであるApp.jsの一番外側に<BrowserRouter>を適用します。
App.js
import { BrowserRouter } from "react-router-dom"; export default function App() { return ( <BrowserRouter> <div className="App"></div> </BrowserRouter> ); }
<Link>タグを使用することで、ハイパーリンクの要素を作成することができます。要素をクリックすると、to属性で指定したパスへ遷移します。(HTMLの<a>タグと同じです。)
import { BrowserRouter, Link } from "react-router-dom"; export default function App() { return ( <BrowserRouter> <div className="App"> <Link to="/">Home</Link> <br /> <Link to="/page1">Page1</Link> <br /> <Link to="/page2">Page2</Link> </div> </BrowserRouter> ); }
次に、パスとコンポーネントの紐づけを行います。
<Routes>タグを使うと、囲った範囲の中で、指定したパス(URL)に紐づいたコンポーネントの切り替えができます。
<Routes>配下では、<Route>タグを使って、パスとコンポーネントの紐づけを行います。path属性にパス(URL)、element属性にコンポーネントを指定します。
import { BrowserRouter, Link, Route, Routes } from "react-router-dom"; import { Home } from "./Home"; import { Page1 } from "./Page1"; import { Page2 } from "./Page2"; export default function App() { return ( <BrowserRouter> <div className="App"> <Link to="/">Home</Link> <br /> <Link to="/page1">Page1</Link> <br /> <Link to="/page2">Page2</Link> </div> <Routes> <Route path="/" element={<Home />} /> <Route path="/page1" element={<Page1 />} /> <Route path="/page2" element={<Page2 />} /> </Routes> </BrowserRouter> ); }

これで、各コンポーネントのリンクをクリックしたとき、対象のコンポーネントへ遷移することができるようになります。
Not Foundページの設定
現状、先ほど指定した以外のURLにアクセスすると、各コンポーネントへのリンク以外、画面に何も表示されません。

これではユーザーに混乱を与えてしまいますので、専用の『Not Found』ページを作成します。 まずは、『Not Found』用のコンポーネントの作成からです。
NotFound.jsx
import { Link } from "react-router-dom"; export const NotFound = () => { return ( <div> <h1>ページが見つかりません</h1> <Link to="/">TOPに戻る</Link> </div> ); };
次に、App.jsへNotFound.jsxへのルーティングを追加します。最初に設定したどのルーティングにも該当しない場合、 『Not Found』ページを出したいので、ルーティングの一番下にpath="*"で指定した<Route>タグを配置します。
App.js
import { BrowserRouter, Link, Route, Routes } from "react-router-dom"; import { Home } from "./Home"; import { NotFound } from "./NotFound"; import { Page1 } from "./Page1"; import { Page2 } from "./Page2"; export default function App() { return ( <BrowserRouter> <div className="App"> <Link to="/">Home</Link> <br /> <Link to="/page1">Page1</Link> <br /> <Link to="/page2">Page2</Link> </div> <Routes> <Route path="/" element={<Home />} /> <Route path="/page1" element={<Page1 />} /> <Route path="/page2" element={<Page2 />} /> {/* 追加 */} <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); }

これで、どのルーティングにも該当しない場合、『Not Found』ページを表示することができました。
ネストされたルーティング
/page1/detailのようなネストされたルーティングを作成します。まずは、Detail.jsxコンポーネントを作成します。
Detail.jsx
export const Detail = () => { return ( <div> <h1>Page1のDetailです</h1> </div> ); };
次に、/page1/detailのルーティングを追加します。
App.jsの/pageへのルーティングを設定している箇所に、ネストしてルーティングを追加します。パスは親ルートからの続きで書くので、path="detail"とします。(/がいらないことに注意!)
App.js
import { BrowserRouter, Link, Route, Routes } from "react-router-dom"; import { Detail } from "./Detail"; import { Home } from "./Home"; import { NotFound } from "./NotFound"; import { Page1 } from "./Page1"; import { Page2 } from "./Page2"; export default function App() { return ( <BrowserRouter> <div className="App"> <Link to="/">Home</Link> <br /> <Link to="/page1">Page1</Link> <br /> <Link to="/page2">Page2</Link> </div> <Routes> <Route path="/" element={<Home />} /> {/* ネストされるルーティングを追加 */} <Route path="/page1" element={<Page1 />}> <Route path="detail" element={<Detail />} /> </Route> <Route path="/page2" element={<Page2 />} /> <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); }
最後に、Page1.jsxに/page1/detailへのリンクと、<Outlet>タグを追加します。<Outlet>は親ルートのコンポーネントに、その子ルートのコンポーネントをどこへレンダリングするか伝えるための要素です。<Outlet>がない場合は正しくレンダリングされないので、注意しましょう。
Page1.js
import { Link, Outlet } from "react-router-dom"; //追加 export const Page1 = () => { return ( <div> <h1>Page1です</h1> {/* 追加 */} <Link to="/page1/detail">Detail</Link> <Outlet /> </div> ); };

これで、ネストされたルーティングも追加することができました。
ルーティング定義の切り分け
さて、今までApp.jsファイルに配下のコンポーネントとルーティングのすべての情報を書いてきましたが、情報量が多くなり、だんだんと見づらくなってきました。
そこで最後に、ルーティングの定義部分だけを別ファイルへ切り出します。src/routerディレクトリを作成し、その直下にRouter.jsxファイルを作成します。
- root
- src
- router
- Router.jsx
- App.js
- Detail.jsx
- Home.jsx
- Page1.jsx
- Page2.jsx
- index.js
次に、App.js内の<Routes>配下を丸ごと切り出し、router/Router.jsx内へ移動させます
Router.jsx
import { Route, Routes } from "react-router-dom"; import { Detail } from "../Detail"; import { Home } from "../Home"; import { NotFound } from "../NotFound"; import { Page1 } from "../Page1"; import { Page2 } from "../Page2"; export const Router = () => { return ( <Routes> <Route path="/" element={<Home />} /> <Route path="/page1" element={<Page1 />}> <Route path="detail" element={<Detail />} /> </Route> <Route path="/page2" element={<Page2 />} /> <Route path="*" element={<NotFound />} /> </Routes> ); };
App.js側では、切り出したRouter.jsxをインポートして、元の場所に配置します。
App.js
import { BrowserRouter, Link } from "react-router-dom"; import { Router } from "./router/Router"; export default function App() { return ( <BrowserRouter> <div className="App"> <Link to="/">Home</Link> <br /> <Link to="/page1">Page1</Link <br /> <Link to="/page2">Page2</Link> </div> {/* 切り出したRouterをインポート */} <Router /> </BrowserRouter> ); }
これで、責務分割ができている見やすいコンポーネントになりましたね。
まとめ
Reactキャッチアップの3週目は、React Routerを使ったルーティングの設定方法でした。
ちなみに、React Router 5系から6系へバージョンアップした際、大幅な変更があったようで、この記事で書いた内容は5系では動きませんので、注意してください。