React Router DOMの活用方法
1. ハッシュモードによるルーティング
ブラウザのURLハッシュを利用した基本的なルーティング実装例です。ハッシュ値の変化を監視してコンテンツを切り替えます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ハッシュルーター</title>
</head>
<body>
<nav>
<a href="#/top">トップ</a>
<a href="#/info">情報</a>
</nav>
<main id="content"></main>
<script>
const contentArea = document.getElementById('content');
window.addEventListener('hashchange', () => {
const path = location.hash.substring(1);
renderContent(path);
});
function renderContent(path) {
switch(path) {
case '/top':
contentArea.innerHTML = '<h1>トップページ</h1>';
break;
case '/info':
contentArea.innerHTML = '<h1>情報ページ</h1>';
break;
default:
contentArea.innerHTML = '<h1>デフォルトページ</h1>';
}
}
// 初期表示
renderContent(location.hash.substring(1) || '/top');
</script>
</body>
</html>
2. History APIを利用したルーティング
HTML5 History APIを使ったよりクリーンなURLの実装例です。pushStateメソッドでURLを制御します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Historyルーター</title>
</head>
<body>
<nav>
<a href="/top" onclick="handleRoute(event)">トップ</a>
<a href="/info" onclick="handleRoute(event)">情報</a>
</nav>
<main id="app"></main>
<script>
const app = document.getElementById('app');
function handleRoute(event) {
event.preventDefault();
const path = event.target.getAttribute('href');
history.pushState({}, '', path);
updateView(path);
}
function updateView(path) {
switch(path) {
case '/top':
app.innerHTML = '<h2>トップ</h2>';
break;
case '/info':
app.innerHTML = '<h2>情報</h2>';
break;
default:
app.innerHTML = '<h2>未設定</h2>';
}
}
window.addEventListener('popstate', () => {
updateView(location.pathname);
});
</script>
</body>
</html>
3. 基本的なルーター設定
React Router DOM v6での基本的な実装方法です。Routesコンポーネントでルートを定義します。
// src/components/MainApp.jsx
import React from 'react';
import { BrowserRouter, Link, Routes, Route, Navigate } from 'react-router-dom';
import { routeConfig } from '@/config/routes';
export function MainApp() {
return (
<BrowserRouter>
<header>
<Link to="/">ホーム</Link>
<Link to="/info">案内</Link>
<Link to="/user/25">ユーザー</Link>
</header>
<Routes>
{routeConfig.map(route => (
<Route
key={route.id}
path={route.path}
element={route.element}
/>
))}
</Routes>
</BrowserRouter>
);
}
// src/config/routes.js
import { TopPage } from '@/pages/TopPage';
import { InfoPage } from '@/pages/InfoPage';
import { UserPage } from '@/pages/UserPage';
import { NotFound } from '@/pages/NotFound';
export const routeConfig = [
{ id: 1, path: '/', element: <Navigate to="/top" replace /> },
{ id: 2, path: '/top', element: <TopPage /> },
{ id: 3, path: '/info', element: <InfoPage /> },
{ id: 4, path: '/user/:userId', element: <UserPage /> },
{ id: 5, path: '*', element: <NotFound /> }
];
4. NavLinkによるスタイリング
NavLinkを使用してアクティブなリンクにスタイルを適用する方法です。
import React from 'react';
import { NavLink, Routes, Route } from 'react-router-dom';
import { routeConfig } from '@/config/routes';
export function Navigation() {
const linkStyle = ({ isActive }) => ({
color: isActive ? 'crimson' : 'inherit',
fontWeight: isActive ? 'bold' : 'normal'
});
const linkClass = ({ isActive }) =>
isActive ? 'nav-link active' : 'nav-link';
return (
<nav>
<NavLink to="/top" style={linkStyle} className={linkClass}>
トップ
</NavLink>
<NavLink to="/info" style={linkStyle} className={linkClass}>
案内
</NavLink>
<Routes>
{routeConfig.map(route => (
<Route key={route.id} path={route.path} element={route.element} />
))}
</Routes>
</nav>
);
}
5. ネストしたルーティングとリダイレクト
親子関係を持つルーティング構造の実装例です。
// src/pages/TopPage.jsx
import React from 'react';
import { Link, Routes, Route, Navigate } from 'react-router-dom';
import { topSubRoutes } from '@/config/routes';
export function TopPage() {
return (
<div>
<aside>
<Link to="/top/products">商品</Link>
<Link to="/top/pricing">料金</Link>
</aside>
<Routes>
{topSubRoutes.map(sub => (
<Route
key={sub.id}
path={sub.path}
element={sub.element}
/>
))}
</Routes>
</div>
);
}
// src/config/routes.js
export const topSubRoutes = [
{ id: 'top-1', path: '*', element: <Navigate to="products" replace /> },
{ id: 'top-2', path: 'products', element: <ProductList /> },
{ id: 'top-3', path: 'pricing', element: <PricingTable /> }
];
6. プログラムによるナビゲーション
useNavigateフックを使ったプログラムによる画面遷移の実装です。
// src/utils/withNavigation.jsx
import { useNavigate } from 'react-router-dom';
export function withNavigation(Component) {
return function WrappedComponent(props) {
const navigate = useNavigate();
return <Component {...props} navigate={navigate} />;
};
}
// src/pages/TopPage.jsx
import React from 'react';
import { withNavigation } from '@/utils/withNavigation';
class TopPageClass extends React.Component {
handleJump = () => {
this.props.navigate('/info', { state: { from: 'top' } });
};
render() {
return (
<div>
<h1>トップページ</h1>
<button onClick={this.handleJump}>案内へ移動</button>
</div>
);
}
}
export const TopPage = withNavigation(TopPageClass);
7. 動的ルーティング
URLパラメータを利用した動的なルーティングの実装です。
// src/config/routes.js
export const routeConfig = [
{ id: 4, path: '/user/:userId', element: <UserDetail /> }
];
// src/pages/UserDetail.jsx
import React from 'react';
import { useParams } from 'react-router-dom';
export function UserDetail() {
const { userId } = useParams();
return (
<div>
<h2>ユーザー詳細</h2>
<p>ユーザーID: {userId}</p>
</div>
);
}
8. パラメータの受け渡し
複数の方法によるパラメータの受け渡し例です。
// クエリパラメータとstate付き遷移
import { Link, useLocation } from 'react-router-dom';
function NavigationButtons() {
return (
<div>
<Link
to={{ pathname: '/info', search: '?source=header' }}
state={{ userType: 'premium' }}
>
詳細情報
</Link>
</div>
);
}
// パラメータの取得
function InfoPage() {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
React.useEffect(() => {
console.log('Query:', queryParams.get('source'));
console.log('State:', location.state);
}, []);
return <div>情報ページ</div>;
}
React Hooksの応用テクニック
1. 基本的なカウンター実装
useStateフックを使ったシンプルなカウンター例です。
import React, { useState } from 'react';
export function SimpleCounter() {
const [number, setNumber] = useState(0);
return (
<div>
<h3>現在の値: {number}</h3>
<button onClick={() => setNumber(prev => prev + 1)}>増加</button>
<button onClick={() => setNumber(prev => prev - 1)}>減少</button>
</div>
);
}
2. 複雑な状態管理
配列やオブジェクトを扱う状態管理の実装例です。
import React, { useState } from 'react';
export function ItemManager() {
const [items, setItems] = useState([]);
const [formData, setFormData] = useState({ title: '', quantity: 1 });
const addItem = () => {
const newItem = {
id: Date.now(),
...formData
};
setItems(prev => [...prev, newItem]);
};
const updateQuantity = (id, delta) => {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, quantity: item.quantity + delta } : item
));
};
return (
<div>
<input
value={formData.title}
onChange={e => setFormData({...formData, title: e.target.value})}
/>
<button onClick={addItem}>追加</button>
<ul>
{items.map(item => (
<li key={item.id}>
{item.title}: {item.quantity}
<button onClick={() => updateQuantity(item.id, 1)}>+</button>
<button onClick={() => updateQuantity(item.id, -1)}>-</button>
</li>
))}
</ul>
</div>
);
}
3. useStateの高度な使い方
初期値の遅延評価と更新関数の活用例です。
import React, { useState } from 'react';
export function AdvancedState() {
// 初期値の遅延評価
const [value, setValue] = useState(() => {
const saved = localStorage.getItem('myKey');
return saved ? JSON.parse(saved) : 0;
});
const handleMultipleUpdates = () => {
// 関数型更新で連続して安全に状態を更新
for (let i = 0; i < 5; i++) {
setValue(prev => prev + i);
}
};
return (
<div>
<div>値: {value}</div>
<button onClick={handleMultipleUpdates}>5回更新</button>
</div>
);
}
4. useEffectの基本的な使い方
副作用の処理とクリーンアップの実装例です。
import React, { useState, useEffect } from 'react';
export function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
const fetchData = async () => {
try {
const response = await fetch('/api/data');
const result = await response.json();
if (isMounted) {
setData(result);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchData();
// クリーンアップ関数
return () => {
isMounted = false;
};
}, []); // 空配列でマウント時のみ実行
if (loading) return <div>読み込み中...</div>;
return <div>{JSON.stringify(data)}</div>;
}
5. 複数のuseEffectの利用
複数の副作用を分離して管理する例です。
import React, { useState, useEffect } from 'react';
export function MultiEffects() {
const [countA, setCountA] = useState(0);
const [countB, setCountB] = useState(0);
const [logs, setLogs] = useState([]);
// 効果A: countAの変化をログに記録
useEffect(() => {
setLogs(prev => [...prev, `効果A: ${countA}`]);
}, [countA]);
// 効果B: countBの変化をログに記録
useEffect(() => {
setLogs(prev => [...prev, `効果B: ${countB}`]);
}, [countB]);
// 効果C: マウント時に一度だけ実行
useEffect(() => {
setLogs(prev => [...prev, '初期マウント']);
}, []);
return (
<div>
<button onClick={() => setCountA(c => c + 1)}>増加A</button>
<button onClick={() => setCountB(c => c + 1)}>増加B</button>
<ul>{logs.map((log, i) => <li key={i}>{log}</li>)}</ul>
</div>
);
}
6. useContextによる状態共有
Contextを使ったグローバル状態の共有例です。
import React, { createContext, useContext } from 'react';
const AuthContext = createContext(null);
const ThemeContext = createContext({ theme: 'light' });
export function AppProviders({ children }) {
const auth = { user: 'takashi', isLoggedIn: true };
return (
<AuthContext.Provider value={auth}>
<ThemeContext.Provider value={{ theme: 'dark' }}>
{children}
</ThemeContext.Provider>
</AuthContext.Provider>
);
}
export function Dashboard() {
const auth = useContext(AuthContext);
const theme = useContext(ThemeContext);
return (
<div style={{ background: theme.theme === 'dark' ? '#333' : '#fff' }}>
ユーザー: {auth.user}
</div>
);
}
7. useReducerによる状態管理
複雑な状態ロジックをuseReducerで整理する例です。
import React, { useReducer } from 'react';
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.payload };
case 'reset':
return initialState;
default:
return state;
}
}
export function CounterWithReducer() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h3>カウント: {state.count}</h3>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<input
type="number"
value={state.step}
onChange={e => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
/>
<button onClick={() => dispatch({ type: 'reset' })}>リセット</button>
</div>
);
}
8. useCallbackによるパフォーマンス最適化
関数の再生成を防ぎ、不要な再レンダリングを抑制する例です。
import React, { useState, useCallback, memo } from 'react';
export function ParentComponent() {
const [value, setValue] = useState(0);
const [flag, setFlag] = useState(false);
// valueが変わらない限り同じ関数インスタンスを維持
const handleIncrement = useCallback(() => {
setValue(v => v + 1);
}, [value]);
// 毎回新しい関数が生成される
const handleToggle = () => {
setFlag(f => !f);
};
return (
<div>
<MemoizedButton onClick={handleIncrement} label="増加" />
<MemoizedButton onClick={handleToggle} label="切替" />
<div>値: {value}, フラグ: {String(flag)}</div>
</div>
);
}
const MemoizedButton = memo(function MemoizedButton({ onClick, label }) {
console.log(`レンダリング: ${label}`);
return <button onClick={onClick}>{label}</button>;
});
9. useMemoによる値のメモ化
高コストな計算結果をメモ化する例です。
import React, { useState, useMemo } from 'react';
function expensiveCalculation(num) {
console.log('計算実行...');
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i * num;
}
return result;
}
export function CalculationComponent() {
const [input, setInput] = useState(1);
const [other, setOther] = useState('');
// inputが変わらない限り計算結果を再利用
const result = useMemo(() => {
return expensiveCalculation(input);
}, [input]);
return (
<div>
<input
type="number"
value={input}
onChange={e => setInput(Number(e.target.value))}
/>
<input
value={other}
onChange={e => setOther(e.target.value)}
/>
<div>結果: {result}</div>
</div>
);
}
10. useRefの活用
DOM要素への参照と過去の値の保持を行う例です。
import React, { useRef, useEffect, useState } from 'react';
export function FocusManager() {
const inputRef = useRef(null);
const prevValueRef = useRef('');
const [text, setText] = useState('');
useEffect(() => {
// テキスト変更時に前の値を保持
prevValueRef.current = text;
}, [text]);
const handleFocus = () => {
inputRef.current?.focus();
};
return (
<div>
<input
ref={inputRef}
value={text}
onChange={e => setText(e.target.value)}
/>
<button onClick={handleFocus}>フォーカス</button>
<div>現在: {text}</div>
<div>以前: {prevValueRef.current}</div>
</div>
);
}
11. forwardRefの使用
子コンポーネントにrefを渡す実装例です。
import React, { forwardRef, useRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
return (
<div>
<label>{props.label}</label>
<input ref={ref} {...props} />
</div>
);
});
export function FormContainer() {
const nameRef = useRef(null);
const handleSubmit = () => {
nameRef.current?.focus();
};
return (
<form onSubmit={handleSubmit}>
<CustomInput ref={nameRef} label="名前" />
<button type="submit">送信</button>
</form>
);
}
12. useImperativeHandleによる制御
親コンポーネントに公開するメソッドを制限する例です。
import React, { forwardRef, useRef, useImperativeHandle } from 'react';
const LimitedInput = forwardRef((props, ref) => {
const internalRef = useRef(null);
useImperativeHandle(ref, () => ({
// 特定のメソッドのみを公開
focusInput: () => {
internalRef.current?.focus();
},
getValue: () => internalRef.current?.value,
clear: () => {
if (internalRef.current) {
internalRef.current.value = '';
}
}
}), []);
return <input ref={internalRef} {...props} />;
});
export function ControlledForm() {
const inputRef = useRef(null);
const handleAction = () => {
inputRef.current?.focusInput();
console.log(inputRef.current?.getValue());
};
return (
<div>
<LimitedInput ref={inputRef} />
<button onClick={handleAction}>アクション</button>
</div>
);
}
13. useLayoutEffectの特性
ブラウザ描画前に同期処理を行う例です。
import React, { useState, useLayoutEffect, useEffect } from 'react';
export function LayoutDemo() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
// DOMへの同期処理(描画前)
const element = document.getElementById('measure');
if (element) {
const rect = element.getBoundingClientRect();
setWidth(rect.width);
}
}, []);
useEffect(() => {
// 非同期処理(描画後)
console.log('描画完了後:', width);
}, [width]);
return (
<div>
<div id="measure">測定要素</div>
<div>幅: {width}px</div>
</div>
);
}
14. カスタムフックの作成
再利用可能なロジックをカスタムフックとして抽出する例です。
// カスタムフック: トグル機能
function useToggle(initial = false) {
const [state, setState] = useState(initial);
const toggle = useCallback(() => setState(s => !s), []);
return [state, toggle];
}
// カスタムフック: ローカルストレージ
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const stored = window.localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
console.error('保存失敗:', e);
}
}, [key, value]);
return [value, setValue];
}
// 使用例
export function CustomHookDemo() {
const [visible, toggleVisible] = useToggle(true);
const [username, setUsername] = useLocalStorage('username', 'ゲスト');
return (
<div>
{visible && <div>表示要素</div>}
<button onClick={toggleVisible}>表示切替</button>
<input
value={username}
onChange={e => setUsername(e.target.value)}
/>
</div>
);
}
15. Fiberアーキテクチャの基礎
Reactの再描画制御メカニズムについての理解です。
requestIdleCallbackを使用した非優先処理の例:
function performLowPriorityTask() {
// ブラウザがアイドル状態になったら実行
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0) {
// 小さなタスクを分割実行
processChunk();
}
}, { timeout: 2000 });
}
16. Hooksの内部構造
Hooksが連結リストとして管理される仕組みの理解です。
Hooksは呼び出し順序に依存しており、条件分岐内での使用は禁止されています。これはReactが各フックの状態を配列のインデックスで追跡するためです。
// 正しい例
function CorrectComponent() {
const [a, setA] = useState(0); // インデックス0
const [b, setB] = useState(1); // インデックス1
if (a > 0) {
// ロジックのみ、フックなし
}
return <div />;
}
// 誤った例(動作しない)
function IncorrectComponent() {
const [a, setA] = useState(0);
if (a > 0) {
// エラー: 条件内でフック使用
const [b, setB] = useState(1);
}
return <div />;
}