6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React Router v7 入門 — 第1回:押さえておくべき基本

6
Posted at

image.png

React RouterはReactエコシステムで最も広く使われているルーティングライブラリです。Reactを触ったことがあれば一度は使ったことがあるはずですが、v7のリリースでいくつかの重要な変更が入ったこともあり、基礎から改めて整理する価値があります。

この記事では基本的な内容に絞って解説します。セットアップ、ナビゲーション、動的ルート、ネストされたルート、そしてよく使うフックが対象です。v5/v6からの移行を検討している方にも適した内容です。

動作環境: React Router v7、TypeScript、React 18+
v7からはすべてのimportが react-router からになりました。react-router-domreact-router に統合されています。


インストール

npm install react-router

v6からの移行であれば、import元を react-router-dom から react-router に一括置換するだけで対応できます。


基本セットアップ

Step 1: アプリを BrowserRouter でラップする

// main.tsx
import { BrowserRouter } from 'react-router'
import { createRoot } from 'react-dom/client'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
)

BrowserRouter はHTML5 History APIを使ってURLとUIを同期させます。ページリロードなしにURLを変更できるのはこの仕組みのおかげです。BrowserRouter はルート(通常は main.tsx または index.tsx)に1つだけ配置してください。

Step 2: App.tsx でルートを定義する

// App.tsx
import { Routes, Route } from 'react-router'
import Home from './pages/Home'
import About from './pages/About'
import Contact from './pages/Contact'
import NotFound from './pages/NotFound'

export default function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/contact" element={<Contact />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  )
}

path="*" はワイルドカードで、上記いずれのルートにもマッチしないURLをすべて受け取ります。404ページの実装に使います。


ナビゲーション: LinkNavLink

import { Link, NavLink } from 'react-router'

export function Navbar() {
  return (
    <nav>
      <Link to="/">ホーム</Link>
      <Link to="/about">About</Link>

      <NavLink
        to="/contact"
        className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
      >
        Contact
      </NavLink>
    </nav>
  )
}
コンポーネント 使いどころ
Link シンプルなページ遷移
NavLink 現在のルートに応じてメニュー項目をハイライトしたい場合

注意: <a href> ではなく必ず <Link> を使ってください。<a> タグはページ全体をリロードしてしまい、SPAのステートが失われます。


動的ルート

/users/123/posts/react-router-guide のように、URLに動的な値を含む場合に使います。

// App.tsx
<Route path="/users/:userId" element={<UserDetail />} />
<Route path="/posts/:slug" element={<PostDetail />} />
// pages/UserDetail.tsx
import { useParams } from 'react-router'

export default function UserDetail() {
  const { userId } = useParams<{ userId: string }>()

  return <h1>ユーザー詳細 #{userId}</h1>
}

useParamspath で定義したすべてのパラメータをオブジェクトとして返します。TypeScriptではジェネリック型を渡すことで型安全に扱えます。

// 複数パラメータの例: /posts/:postId/comments/:commentId
const { postId, commentId } = useParams<{
  postId: string
  commentId: string
}>()

ネストされたルートと共有レイアウト

React Routerの中でも特に重要なコンセプトです。各ページにレイアウトを繰り返し書く代わりに、共通レイアウトコンポーネントを1つ定義し、React Routerが適切な位置にページコンテンツを差し込みます。

// App.tsx
import DashboardLayout from './layouts/DashboardLayout'
import DashboardHome from './pages/DashboardHome'
import Settings from './pages/Settings'
import Profile from './pages/Profile'

export default function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />

      <Route path="/dashboard" element={<DashboardLayout />}>
        <Route index element={<DashboardHome />} />        {/* /dashboard */}
        <Route path="settings" element={<Settings />} />   {/* /dashboard/settings */}
        <Route path="profile" element={<Profile />} />     {/* /dashboard/profile */}
      </Route>
    </Routes>
  )
}
// layouts/DashboardLayout.tsx
import { Outlet, NavLink } from 'react-router'

export default function DashboardLayout() {
  return (
    <div style={{ display: 'flex' }}>
      <aside>
        <NavLink to="/dashboard">概要</NavLink>
        <NavLink to="/dashboard/settings">設定</NavLink>
        <NavLink to="/dashboard/profile">プロフィール</NavLink>
      </aside>

      <main>
        <Outlet />  {/* 子ルートのコンポーネントがここにレンダリングされる */}
      </main>
    </div>
  )
}

2つの重要なコンセプト:

<Outlet /> — React Routerが現在の子ルートのコンポーネントをレンダリングする場所です。Outlet を置き忘れると、ルート設定が正しくても子ルートが表示されません。

<Route index> — 親ルートのpathと同じURLに対応します。/dashboard にアクセスしたとき、DashboardLayoutOutlet の中に DashboardHome がレンダリングされます。


useNavigate — コードからのページ遷移

フォームの送信成功後、ログイン完了後、レコード削除後など、イベントに応じてプログラムからナビゲートしたいときに使います。

import { useNavigate } from 'react-router'

export function LoginForm() {
  const navigate = useNavigate()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    try {
      await login(credentials)

      // replace: true — ログインページをhistoryスタックから削除する
      // ユーザーがBackボタンでログインページに戻れなくなる
      navigate('/dashboard', { replace: true })

    } catch (error) {
      console.error('ログインに失敗しました')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">ログイン</button>
    </form>
  )
}

よく使うパターンをまとめると:

navigate('/dashboard')                    // 通常の遷移
navigate('/dashboard', { replace: true }) // 現在のhistoryエントリを置き換える
navigate(-1)                              // 1つ前のページに戻る(ブラウザのBackと同等)
navigate(1)                               // 1つ先のページに進む

useSearchParams — クエリ文字列の管理

クエリ文字列とはURLの ? 以降の部分のことです(例: /products?page=2&category=shoes)。新しいルートを追加せずにUIの状態をURLに保持する手段として有効で、URLをそのまま共有すれば同じページ・同じフィルター状態を再現できます。

import { useSearchParams } from 'react-router'

export function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams()

  const page = Number(searchParams.get('page') ?? '1')
  const category = searchParams.get('category') ?? ''

  const handlePageChange = (newPage: number) => {
    setSearchParams({ page: String(newPage), category })
  }

  const handleCategoryChange = (newCategory: string) => {
    // カテゴリ変更時はpage 1にリセット
    setSearchParams({ page: '1', category: newCategory })
  }

  return (
    <div>
      <select
        value={category}
        onChange={(e) => handleCategoryChange(e.target.value)}
      >
        <option value="">すべて</option>
        <option value="shoes">シューズ</option>
        <option value="shirts">シャツ</option>
      </select>

      {/* 商品一覧 */}

      <button onClick={() => handlePageChange(page + 1)}>
        次のページ →
      </button>
    </div>
  )
}

Tip: setSearchParamsuseState と同様に動作し、クエリ文字列を全体ごと置き換えます。特定のパラメータだけ更新して他を保持したい場合は、関数形式で渡してください:

setSearchParams(prev => {
  prev.set('page', String(newPage))
  return prev
})

まとめ

やりたいこと 使うもの
ルーターのセットアップ <BrowserRouter> でアプリをラップ
ルートの定義 <Routes> + <Route>
ページリンク <Link to="...">
アクティブ状態付きリンク <NavLink> + className={({ isActive }) => ...}
動的パラメータを含むルート path="/users/:id" + useParams<{ id: string }>()
共通レイアウト ネストされたルート + <Outlet />
グループのデフォルトルート <Route index>
コードからの遷移 useNavigate
クエリ文字列の読み書き useSearchParams
404ページ <Route path="*">

ここで紹介した内容は基礎となる部分です。より複雑なパターンに進む前に、これらをしっかり理解しておくことをおすすめします。第2回では、loader / action を使ったData mode、Protected Routes、Lazy Loading、そして大規模プロジェクトでのルート設計について解説します。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?