振り返りようです。
// filepath: [useNavigate.ts](http://_vscodecontentref_/0)
import { useCallback, useRef } from "react";
import { useRouter, Href } from "expo-router";
/**
* ナビゲーションロック用の内部フック
* @param cooldown クールダウン時間(ミリ秒)
*/
function useNavigationLock(cooldown: number = 500) {
const isNavigating = useRef(false);
// ✅ 環境に依存しない型定義
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const runWithLock = useCallback(
(action: () => void) => {
// 連打防止
if (isNavigating.current) {
if (__DEV__) {
console.log("⚠️ ナビゲーション中のため、クリックを無視");
}
return;
}
// ロック開始
isNavigating.current = true;
try {
// アクション実行
action();
} catch (error) {
// エラーログ出力
if (__DEV__) {
console.error("❌ ナビゲーションエラー:", error);
}
} finally {
// 🐤 必ずロック解除→例外発生時も確実に実行
// 既存のタイマーをクリア
if (timeoutRef.current !== null) {
clearTimeout(timeoutRef.current);
}
// 🐤 型キャスト不要
timeoutRef.current = setTimeout(() => {
isNavigating.current = false;
if (__DEV__) {
console.log("✅ ナビゲーションロック解除");
}
}, cooldown);
}
},
[cooldown]
);
return runWithLock;
}
/**
* push 版
*/
export function useNavigate(cooldown: number = 500) {
const router = useRouter();
const runWithLock = useNavigationLock(cooldown);
const navigate = useCallback(
(path: Href) => {
runWithLock(() => {
router.push(path);
});
},
[router, runWithLock]
);
return navigate;
}
/**
* replace 版
*/
export function useNavigateReplace(cooldown: number = 500) {
const router = useRouter();
const runWithLock = useNavigationLock(cooldown);
const navigate = useCallback(
(path: Href) => {
runWithLock(() => {
router.replace(path);
});
},
[router, runWithLock]
);
return navigate;
}
/**
* back 版
*/
export function useNavigateBack(cooldown: number = 500) {
const router = useRouter();
const runWithLock = useNavigationLock(cooldown);
const goBack = useCallback(() => {
runWithLock(() => {
router.back();
});
}, [router, runWithLock]);
return goBack;
}