準備

TypeScript版のCreate React App を使用します。
下記でプロジェクトディレクトリを作成。

$ yarn create react-app react-router-test --template typescript
$ cd react-router-test

続いてReact Routerライブラリのインストール。

$ yarn add react-router-dom@6

今回使用するバージョンはそれぞれこんな感じです。
typescript: 4.4.2
react: 18.1.0
react-router-dom: 6

一番シンプルなルーティング

最初に単純にページ(コンポーネント)の表示を切り替えるだけのルーティング設定をしてみます。

表示されるページ、ホームページと記事一覧ページコンポーネントを作ります。

src/pages/Home.tsx

import React from 'react'

const Home: React.FC = () => {
	return (
		<div>
			<h1>ホーム</h1>
		</div>
	)
}

export default Home

src/pages/Posts.tsx

import React from 'react'

const Posts: React.FC = () => {
	return (
		<div>
			<h1>記事一覧</h1>
		</div>
	)
}

export default Posts

この二つのページを切り替えられるようにルーターの設定をします。
App.tsxを次のように編集します。

src/App.tsx

import React from 'react'
import {
	BrowserRouter,
	Routes,
	Route,
} from 'react-router-dom'
import Home from './pages/Home'
import Posts from './pages/Posts'

const App: React.FC = () => {
	return (
		<BrowserRouter>
			<Routes>
				<Route path="/" element={<Home />} />
				<Route path="posts" element={<Posts />} />
			</Routes>
		</BrowserRouter>
	)
}

export default App

Routepathにアクセスしたパス、elementに表示させるコンポーネントを指定します。
ブラウザで「/」「/posts」にアクセスしてそれぞれのコンポーネントが表示されることを確認してください。

パス コンポーネント
/ Homeコンポーネント
/posts Postsコンポーネント

次はリンクを追加してそれぞれのページに遷移できるようにしてみましょう。

src/pages/Home.tsx

import React from 'react'
import { Link } from 'react-router-dom'

const Home: React.FC = () => {
	return (
		<div>
			<ul>
				<li><Link to="/">ホーム</Link></li>
				<li><Link to="/posts">記事一覧</Link></li>
			</ul>
			<h1>ホーム</h1>
		</div>
	)
}

export default Home

Postsコンポーネントにも同じように修正してください。
Linkコンポーネントのtoroutesで設定したパスを指定することで、指定したコンポーネントに遷移します。

aタグではなくLinkを使用することでブラウザの遷移が発生しなくなり高速で切り替えることができます。

アウトレット(outlet)機能で共通コンポーネントの設定

ナビゲーションは全ページで共通で使いたいですね。
レウアウトコンポーネントを作って、それを各コンポーネントで配置するという方法もあると思いますが、ここではアウトレット(outlet)という機能を使用してみます。

最初にレイアウト用のコンポーネントを作成します。
先ほど作成したリンクナビゲーションはここに配置します。
その下にOutletを配置します。最終的にはここに各ページ用のコンポーネントが表示されます。

ちなみに共通で使用するナビゲーションはlinkではなくNavLinkを使用することで現在のページにactiveクラスを付けてくれます。

src/components/Layout.tsx

import React from 'react'
import { NavLink, Outlet } from 'react-router-dom'

const Layout: React.FC = () => {
	return (
		<div>
			<ul>
				<li><NavLink to="/">ホーム</NavLink></li>
				<li><NavLink to="/posts">記事一覧</NavLink></li>
			</ul>
			<Outlet />
		</div>
	)
}

export default Layout

アウトレットに表示されるようにルーターを次のように編集します。

src/App.tsx

<Routes>
	<Route path="/" element={<Layout />}>
		<Route index element={<Home />} />
		<Route path="posts" element={<Posts />} />
	</Route>
</Routes>

アウトレットに表示させたいコンポーネントを子に配置するだけです。

各ページコンポーネントではナビゲーションが必要なくなったので削除しておきましょう。

src/pages/Home.tsx

import React from 'react'

const Home: React.FC = () => {
	return (
		<div>
			<h1>ホーム</h1>
		</div>
	)
}

export default Home

Postsコンポーネントも同じように編集してください。
これで同様の結果になります。各ページコンポーネントがすっきりしましたね。

下層ページのルート設定

次に記事一覧の下に記事詳細ページを作ってみます。
記事詳細はURLからIDを取得して一つのページコンポーネントを動的に切り替える想定です。

ルーターから編集しましょう。

src/App.tsx

<Routes>
	<Route path="/" element={<Layout />}>
		<Route index element={<Home />} />
		<Route path="posts" element={<Posts />} />
		<Route path="posts/:postId" element={<Post />} />
	</Route>
</Routes>

新しくPostコンポーネントを項目を追加してます。
詳細ページURLからパラメータを取得するのでパスには:postIdとコロンをつけてキーワードを指定します。
これで「posts/1」「posts/2」にアクセスしたとき、postIdというパラメータで「1,2」が取れるようになります。

今回は詳細ページには共通でリンクを表示しないのでアウトレットは使用しないで、ルーターは並列に設定します。もし表示したい場合は入れ子にしてください。

次に記事詳細ページのPostコンポーネントを作ります。
ここでは受け取ったIDをそのまま表示してみます。
パラメータはuseParamsで取得することができます。

src/pages/Post.tsx

import React from 'react'
import { useParams } from 'react-router-dom'

const Post: React.FC = () => {
	const { postId } = useParams()
	
	return (
		<div>
			<h2>投稿詳細</h2>
			<p>ID: { postId }</p>
		</div>
	)
}

export default Post

詳細ページの準備ができたので、一覧ページから遷移できるようにリンクを追加しましょう。

src/pages/Posts.tsx

const Posts: React.FC = () => {
	return (
		<div>
			<h1>記事一覧</h1>
			<ul>
				<li><Link to="/posts/1">記事1</Link></li>
				<li><Link to="/posts/2">記事2</Link></li>
				<li><Link to="/posts/3">記事3</Link></li>
			</ul>
		</div>
	)
}

これで各詳細ページへ遷移できるようになります。

クエリパラメータの取得

絞り込み検索を行う場合は、URLからクエリパラメータを取得したいですね。
その場合はuseSearchParamsというフックを使用します。

たとえば?title=hogeという値を取りたい場合は次のようにします。

const [searchParams] = useSearchParams()
console.log(searchParams.get('itle'))

遅延ロード

規模が大きくなるとWebpackなどでビルドしたファイルサイズが肥大化し初回の読み込み時間が長くなるという問題があります。
ReactのSuspenseを組み込むことで遅延ロードができます。

src/App.tsx

import React, { lazy, Suspense } from 'react'
import {
	BrowserRouter,
	Routes,
	Route
} from 'react-router-dom'
import Layout from './components/Layout'

const Home  = lazy(() => import('./pages/Home'))
const Posts = lazy(() => import('./pages/Posts'))
const Post  = lazy(() => import('./pages/Post'))

const App: React.FC = () => {
	return (
	<BrowserRouter>
		<Routes>
			<Route path="/" element={<Layout />}>
				<Route index element={
					<Suspense fallback={<p>Loading...</p>}>
						<Home />
					</Suspense>
				} />
				<Route path="posts" element={
					<Suspense fallback={<p>Loading...</p>}>
						<Posts />
					</Suspense>
				} />
				<Route path="posts/:postId" element={
					<Suspense fallback={<p>Loading...</p>}>
						<Post />
					</Suspense>
				} />
			</Route>
		</Routes>
	</BrowserRouter>
	)
}

export default App

ルーティングをオブジェクト形式で設定

今までDOMでルーティングの設定をしましたが、オブジェクト形式でも設定することができます。
新しくルーティング用ファイルを作成して設定してみましょう。

src/routes.tsx

import type { RouteObject } from 'react-router-dom'
import Layout from './components/Layout'
import Home from './pages/Home'
import Posts from './pages/Posts'
import Post from './pages/Post'
import NotFound from './pages/NotFound'

export const routes: RouteObject[] = [
	{
		path: '/',
		element: <Layout />,
		children: [
			{
				index: true,
				element: <Home />
			}, {
				path: '/posts',
				element: <Posts />
			}, {
				path: '/posts/:postId',
				element: <Post />
			}, {
				path: '*',
				element: <NotFound />
			}
		]
	}
]

作成したroutes.tsxApp.tsxに配置します。

src/routes.tsx

import React from 'react'
import { useRoutes } from 'react-router-dom'
import { routes } from './routes'

const App: React.FC = () => {
  const router = useRoutes(routes)

  return (
    <>{router}</>
  )
}

export default App

BrowserRouterindex.tsxに配置します。

src/index.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import reportWebVitals from './reportWebVitals'
import { BrowserRouter } from 'react-router-dom'

const root = ReactDOM.createRoot(
	document.getElementById('root') as HTMLElement
)
root.render(
	<React.StrictMode>
		<BrowserRouter>
			<App />
		</BrowserRouter>
	</React.StrictMode>
)
reportWebVitals()

React Routerにはその他にもいろいろな機能があります。
詳しくは下記ドキュメントをご確認ください。

React Router | Docs Home