NextJS13中导入next/router的SingletonRouter为Null问题求助
解决Next.js中未保存修改时阻止路由变更的问题
你遇到的next/router单例返回null的情况,是因为Next.js 13+的App Router(app目录)不兼容旧的next/router API。旧路由系统仅适用于Pages Router(pages目录),下面分两种路由场景给出具体解决方案:
一、Pages Router(pages目录)场景
在Pages Router中,正确使用next/router拦截路由变更的方式如下:
实现代码
import { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; const EditPage = () => { const router = useRouter(); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); // 拦截内部路由跳转 useEffect(() => { const handleRouteChange = (targetUrl) => { if (hasUnsavedChanges) { // 取消当前路由变更 router.events.emit('routeChangeError', new Error('导航已取消'), targetUrl); if (window.confirm('你有未保存的修改,确定要离开吗?')) { // 用户确认后允许跳转 router.push(targetUrl); } } }; router.events.on('routeChangeStart', handleRouteChange); // 组件卸载时移除监听,避免内存泄漏 return () => router.events.off('routeChangeStart', handleRouteChange); }, [hasUnsavedChanges, router]); // 拦截页面刷新/关闭 useEffect(() => { const handleBeforeUnload = (e) => { if (hasUnsavedChanges) { e.preventDefault(); e.returnValue = '你有未保存的修改,确定要离开吗?'; } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [hasUnsavedChanges]); return ( <div> <input type="text" onChange={() => setHasUnsavedChanges(true)} placeholder="输入内容..." /> <button onClick={() => setHasUnsavedChanges(false)}>保存修改</button> </div> ); }; export default EditPage;
二、App Router(app目录)场景
App Router使用next/navigation提供的新路由API,没有全局Router单例,需要通过自定义导航拦截+状态共享实现需求:
1. 创建未保存状态上下文
// app/contexts/UnsavedChangesContext.jsx 'use client'; import { createContext, useState, useContext } from 'react'; const UnsavedChangesContext = createContext(); export const UnsavedChangesProvider = ({ children }) => { const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); return ( <UnsavedChangesContext.Provider value={{ hasUnsavedChanges, setHasUnsavedChanges }}> {children} </UnsavedChangesContext.Provider> ); }; export const useUnsavedChanges = () => useContext(UnsavedChangesContext);
2. 自定义拦截式Link组件
// app/components/CustomLink.jsx 'use client'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useUnsavedChanges } from '../contexts/UnsavedChangesContext'; const CustomLink = ({ href, children, ...props }) => { const router = useRouter(); const { hasUnsavedChanges } = useUnsavedChanges(); const handleClick = (e) => { if (hasUnsavedChanges) { e.preventDefault(); if (window.confirm('你有未保存的修改,确定要离开吗?')) { router.push(href); } } }; return ( <Link href={href} onClick={handleClick} {...props}> {children} </Link> ); }; export default CustomLink;
3. 封装安全导航函数(处理程序式跳转)
// app/utils/safeNavigation.js 'use client'; import { useRouter } from 'next/navigation'; import { useUnsavedChanges } from '../contexts/UnsavedChangesContext'; export const useSafeNavigation = () => { const router = useRouter(); const { hasUnsavedChanges } = useUnsavedChanges(); const push = (href) => { if (hasUnsavedChanges) { window.confirm('你有未保存的修改,确定要离开吗?') && router.push(href); } else { router.push(href); } }; const replace = (href) => { if (hasUnsavedChanges) { window.confirm('你有未保存的修改,确定要离开吗?') && router.replace(href); } else { router.replace(href); } }; return { push, replace }; };
4. 拦截浏览器前进后退
// app/components/UnsavedChangesHandler.jsx 'use client'; import { useEffect } from 'react'; import { usePathname } from 'next/navigation'; import { useUnsavedChanges } from '../contexts/UnsavedChangesContext'; const UnsavedChangesHandler = () => { const { hasUnsavedChanges } = useUnsavedChanges(); const currentPath = usePathname(); // 拦截浏览器前进后退 useEffect(() => { const handlePopState = () => { if (hasUnsavedChanges) { // 重置路由到当前页面,阻止默认回退 history.pushState(null, '', currentPath); if (window.confirm('你有未保存的修改,确定要离开吗?')) { history.back(); } } }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, [hasUnsavedChanges, currentPath]); // 拦截页面刷新/关闭 useEffect(() => { const handleBeforeUnload = (e) => { if (hasUnsavedChanges) { e.preventDefault(); e.returnValue = '你有未保存的修改,确定要离开吗?'; } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [hasUnsavedChanges]); return null; }; export default UnsavedChangesHandler;
5. 在根布局中注册上下文和处理器
// app/layout.jsx import { UnsavedChangesProvider } from './contexts/UnsavedChangesContext'; import UnsavedChangesHandler from './components/UnsavedChangesHandler'; export default function RootLayout({ children }) { return ( <html lang="zh-CN"> <body> <UnsavedChangesProvider> <UnsavedChangesHandler /> {children} </UnsavedChangesProvider> </body> </html> ); }
6. 在表单页面使用状态
// app/edit/page.jsx 'use client'; import { useUnsavedChanges } from '../contexts/UnsavedChangesContext'; import CustomLink from '../components/CustomLink'; import { useSafeNavigation } from '../utils/safeNavigation'; const EditPage = () => { const { setHasUnsavedChanges } = useUnsavedChanges(); const { push } = useSafeNavigation(); return ( <div> <input type="text" onChange={() => setHasUnsavedChanges(true)} placeholder="输入内容..." /> <button onClick={() => setHasUnsavedChanges(false)}>保存修改</button> {/* 使用自定义Link */} <CustomLink href="/">返回首页</CustomLink> {/* 使用安全导航函数处理程序式跳转 */} <button onClick={() => push('/')}>程序式返回首页</button> </div> ); }; export default EditPage;
内容的提问来源于stack exchange,提问作者Lucepall
相关产品推荐
相关产品推荐

