React Router v6でのルート変更の検知方法
問題点
React Router v5では、useHistory
フックを使用してルート変更を監視することができました:
const history = useHistory();
history.listen((location, action) => {
// ルート変更時の処理
});
しかし、v6ではuseHistory
がuseNavigate
に置き換えられ、navigate
オブジェクトにはlisten
メソッドが存在しないため、同じ方法は使えません:
// v6では機能しない
const navigate = useNavigate();
navigate.listen(...); // TypeError: listen is not a function
解決策
方法1: useLocationフックを使用する(推奨)
最もシンプルで公式に推奨される方法は、useLocation
フックを使用することです:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function MyComponent() {
const location = useLocation();
useEffect(() => {
// ルートが変更されるたびに実行される
console.log('Route changed:', location.pathname);
// ここでGoogle Analyticsなどのトラッキング処理を実行
ga('send', 'pageview');
}, [location]); // locationが変更されるたびに効果が発動
return <div>コンテンツ</div>;
}
TIP
この方法は、コンポーネントがアンマウントされない限り有効です。ナビゲーションによってコンポーネントがアンマウントされる場合、useEffectは実行されません。
方法2: ナビゲーションタイプの検出
ブラウザの戻る/進むボタンなど、特定のナビゲーションアクションを検出したい場合は、useNavigationType
フックを併用します:
import { useEffect } from 'react';
import { useLocation, useNavigationType } from 'react-router-dom';
export const useBackListener = (callback) => {
const location = useLocation();
const navType = useNavigationType();
useEffect(() => {
if (navType === 'POP' && location.key !== 'default') {
// 戻る/進むボタンが押された時の処理
callback();
}
}, [location, navType, callback]);
};
// 使用例
function MyComponent() {
useBackListener(() => {
console.log('戻るボタンが押されました');
});
return <div>コンテンツ</div>;
}
方法3: カスタムHistoryオブジェクトの使用(上級者向け)
高度なユースケースでは、カスタムhistoryオブジェクトを作成することも可能です:
// まずhistoryパッケージをインストール
// npm install history@5
// myHistory.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
// CustomRouter.js
import { useState, useLayoutEffect } from 'react';
import { Router } from 'react-router-dom';
export const CustomRouter = ({ history, ...props }) => {
const [state, setState] = useState({
action: history.action,
location: history.location
});
useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
{...props}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
};
// App.js
import { CustomRouter } from './CustomRouter';
import history from './myHistory';
function App() {
return (
<CustomRouter history={history}>
{/* ルート設定 */}
</CustomRouter>
);
}
// コンポーネント内での使用
import { useEffect } from 'react';
import history from './myHistory';
function MyComponent() {
useEffect(() => {
const unlisten = history.listen(({ location, action }) => {
console.log('Route changed:', location.pathname);
});
return unlisten; // クリーンアップ関数
}, []);
return <div>コンテンツ</div>;
}
注意
この方法は複雑であり、React Routerの将来のバージョンで動作しなくなる可能性があります。公式のuseLocation
を使用する方法を優先してください。
方法4: unstable_HistoryRouterの使用(試験的)
v6.4以降では、試験的なunstable_HistoryRouter
コンポーネントが提供されています:
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
import history from './myHistory';
function App() {
return (
<HistoryRouter history={history}>
{/* ルート設定 */}
</HistoryRouter>
);
}
警告
unstable
という接頭辞が示す通り、このAPIは不安定であり、将来のバージョンで変更または削除される可能性があります。プロダクションコードでの使用は慎重に検討してください。
まとめ
- 基本的なルート変更検知には
useLocation
+useEffect
を使用する - ナビゲーションタイプの識別が必要な場合は
useNavigationType
を併用する - 高度な制御が必要な場合のみカスタムhistoryオブジェクトを検討する
- 試験的APIは将来の互換性に注意して使用する
React Router v6では、シンプルなuseLocation
アプローチがほとんどのユースケースをカバーし、コードの可読性と保守性も向上します。