【React】React Router v6の使い方 - URLによって表示を切り替える
今回はReact Router v6の基本的な使い方について説明します。URLによって表示を切り替えるときに必要な機能です。
前提条件は以下
- VSCode上でcreat react appを使ってReact、TypeScriptの環境で行う
- ESLint(Airbnb)とPrettierを追加して、VSCode上の拡張機能と連動させている
環境は以下です。
- macOS Catalina v10.15.5
- Visual Studio Code v1.57.0
- React v17.0.2
- TypeScript v4.5.5
- react-router-dom v6.2.2
- node.js v16.13.1
React Router v6の使い方
ReactでURLごとに表示を切り替えるためにはReact Routerを使う必要があります。基本的な開発環境については以下の記事に沿って作成しています。
React Routerをインストール
以下のコマンドからReact Routerと型をインストールします。
npm i --save react-router-dom @types/react-router-dom
React Routerについては以下の公式をどうぞ。
説明するディレクトリ構造
今回は以下のディレクトリ構造で説明します。
ページ数は全部で4ページです。
- トップページ(URLは"/")
- page1(URLは"/page1")
- page1の子ページ(URLは"/page1/page1Child")
- page1の孫ページ(URLは"/page1/page1Child/page1Grandchild")
|_public/
| |- index.html
|
|_src/
|- assets/ (共通で使うCSSや画像をまとめている)
| |- css/
| | |- _base.scss
| | |- _custom-property.scss
| | |- styles.scss
| |
| |- img/
|
|- components/(コンポーネントをまとめている)
| |- blocks/(ページを構成するコンポーネント)
| |
| |- layout/(大きいレイアウトを構成するコンポーネント)
| | |- Layout.module.scss
| | |- Layout.tsx
| |
| |- pages/(全ページを管理する)
| | |- home/(トップページ)
| | | |- Home.module.scss
| | | |- Home.tsx
| | |
| | |- page1/(page1ページ)
| | | |- Page1.module.scss
| | | |- Page1.tsx
| | |
| | |- page1Child/(page1の子ページ)
| | | |- Page1Child.module.scss
| | | |- Page1Child.tsx
| | |
| | |- page1Grandchild/(page1の孫ページ)
| | | |- Page1Grandchild.module.scss
| | | |- Page1Grandchild.tsx
| | |
| |
| |- types/(共通で使う型をまとめている)
|
|- App.tsx
|- index.tsx
基本的な書き方
App.tsxでURLにごとに表示を切り替えるとします。
例としてトップページとpage1ページを切り替える場合です。
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Home } from './components/pages/home/Home';
import { Page1 } from './components/pages/page1/Page1';
const App = () => (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="page1" element={<Page1 />} />
</Routes>
</BrowserRouter>
);
export default App;
<Route>ではpathにルートパスを指定して、elementではそのルートパスで表示させたいコンポーネント指定します。
つまり、URLが"/"のトップページでは<Home/>が表示され、URLが"/page1"では<Page1/>が表示されるということ。
そして<Route>の部分は<Routes>と<BrowserRouter>で囲われている必要があります。
リンクをクリックして表示を切り替える
Homeコンポーネントにリンクを設置するには以下。
import { Link } from 'react-router-dom';
export const Home = () => (
<>
<div>これはトップページです</div>
<nav>
<ul>
<li>
<Link to="/">トップページです</Link>
</li>
<li>
<Link to="page1">Page1ページです</Link>
</li>
</ul>
</nav>
</>
);
<Link>を使ってtoの部分にパスを指定します。
つまり、toにpage1が指定されているLinkをクリックすると、URLがpage1に切り替わり、<Route>に指定されているpathが"page1"と一致するため、elementにある<Page1/>が表示されるということ。
URLが深い階層の場合
例えばpage1ページに下層ページがある場合について説明します。
URLが"page1/page1Child"のような場合で、page1Childページを表示させる場合です。
- page1 … 親ページ
- page1Child … page1の子ページ
App.tsxでは以下のように書きます。
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Layout } from './components/layout/Layout';
import { Home } from './components/pages/home/Home';
import { Page1 } from './components/pages/page1/Page1';
import { Page1Children } from './components/pages/page1Child/Page1Child';
const App = () => (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="page1" element={<Layout />}>
<Route index element={<Page1 />} />
<Route path="page1Child" element={<Page1Child />} />
</Route>
</Routes>
</BrowserRouter>
);
export default App;
11〜14行目で<Route>がネスト化されているのがわかります。
11行目のpathに指定されたURLにアクセスすると、12行目のindexがある<Route>のelementで指定されたコンポーネントが表示されます。
13行目の<Route>のpathで指定されたURLは、11行目のpathで指定されたURLより下の階層に位置することになります。今回だと"page1/page1Child"のURLでアクセスされたときに<Page1Child/>が表示されることになります。
重要なのは11行目のelementの指定された<Layout/>です。
11行目の<Route>で囲まれた2つの<Route>は、この<Layout/>コンポーネントの中にある<Outlet/>が書かれた箇所にelementで指定したコンポーネントが配置されます。
以下は<Layout/>コンポーネントの例です。
import { Outlet } from 'react-router-dom';
import classes from './Layou.module.scss';
export const Layout = () => (
<div className={classes.block}>
<Outlet />
</div>
);
この<Outlet/>の部分にURLが"/page1"のときに<Page1/>コンポーネントが配置されて、URLが"/page1/page1Child"のときは<Page1Child>コンポーネントが配置されることになります。
URLがもっと深い場合は?
URLが"/page1/page1Child/page1GrandChild"の場合で、page1GrandChildページを表示させる場合は以下です。
14〜17行目のようにネストの中にさらにネストする形になります。
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Layout } from './components/layout/Layout';
import { Home } from './components/pages/home/Home';
import { Page1 } from './components/pages/page1/Page1';
import { Page1Children } from './components/pages/page1Child/Page1Child';
import { Page1Grandchild } from './components/pages/page1Grandchild/Page1Grandchild';
const App = () => (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="page1" element={<Layout />}>
<Route index element={<Page1 />} />
<Route path="page1Child" element={<Layout />}>
<Route index element={<Page1Child />} />
<Route path="page1Grandchild" element={<Page1Grandchild />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
);
export default App;
存在しないURLの場合は404ページを表示
存在しないURLにアクセスされたときに404ページを表示させる場合は以下です。
pathに*(アスタリスク)を指定します。
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Home } from './components/pages/home/Home';
import { Page1 } from './components/pages/page1/Page1';
import { NotFound } from './components/pages/notFound/NotFound';
const App = () => (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="page1" element={<Page1 />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
export default App;
コンポーネントにデータを渡すには?
elementに指定されているコンポーネントにpropsを使ってデータを渡すこともできます。
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Home } from './components/pages/home/Home';
import { Page1 } from './components/pages/page1/Page1';
const App = () => (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home testMessage="トップページです" />} />
</Routes>
</BrowserRouter>
);
export default App;
8行目で<Home/>コンポーネントに「トップページです」という文字列を渡しています。この文字列を<Home/>コンポーネントで表示させるのは以下です。
import { VFC } from 'react';
type Props = {
testMessage: string;
};
export const Home: VFC<Props> = (props) => {
const { testMessage } = props;
return (
<div>
<div>{testMessage}</div>
</div>
);
};
レイアウト用にRouteを使う
以下のように単純にレイアウト用として<Route>を使うこともできます。
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { BaseLayout } from './components/layout/BaseLayout';
import { Layout } from './components/layout/Layout';
import { Home } from './components/pages/home/Home';
import { Page1 } from './components/pages/page1/Page1';
import { Page2 } from './components/pages/page2/Page2';
const App = () => (
<BrowserRouter>
<Routes>
<Route element={<BaseLayout />}>
<Route path="/" element={<Home />} />
<Route path="page1" element={<Page1 />} />
</Route>
<Route element={<Layout />}>
<Route path="page2" element={<Page2 />} />
</Route>
</Routes>
</BrowserRouter>
);
export default App;
<Home/>コンポーネントと<Page1/>コンポーネントは、<BaseLayout/>の中の<OutLet/>の部分に表示されます。
<Page2/>コンポーネントは<Layout/>の中の<OutLet/>の部分に表示されます。
さいごに
今回はReact Router v6の基本的な使い方について説明しました。v5に比べてv6は書き方がシンプルになって分かりやすくなった印象ですね。
Reactで複数のページを作成するときに参考にしてみてください。