React-Router-Dom v6实现编辑页跳转前未保存更改提示
解决方案:React Router v6 下路由跳转时的未保存更改确认
你之前的beforeunload监听仅对浏览器刷新/关闭标签页有效,但React Router的单页内部路由跳转不会触发这个事件。下面提供两种适配React Router v6的实现方案:
方案一:使用官方不稳定API unstable_useBlocker(推荐)
这个API能全局拦截所有路由跳转行为,覆盖浏览器前进后退、导航链接点击等所有场景,虽然标记为"不稳定",但实际项目中已被广泛使用,后续大概率会转为稳定API。
完整代码示例
import { useState, useEffect } from 'react'; import { unstable_useBlocker as useBlocker, useNavigate, Link } from 'react-router-dom'; const PersonEditPage = () => { const navigate = useNavigate(); // 模拟从接口获取的原始表单数据 const [originalFormData, setOriginalFormData] = useState({ name: '张三', age: 25 }); const [formData, setFormData] = useState(originalFormData); // 判断表单是否有未保存更改 const isDirty = JSON.stringify(formData) !== JSON.stringify(originalFormData); // 拦截路由跳转:当有未保存更改且跳转到其他路由时阻塞 const blocker = useBlocker(({ currentLocation, nextLocation }) => { return isDirty && currentLocation.pathname !== nextLocation.pathname; }); // 处理阻塞后的确认逻辑 useEffect(() => { if (blocker.state === 'blocked') { const shouldLeave = window.confirm('是否离开页面?你已做出更改,离开将丢失未保存内容。'); shouldLeave ? blocker.proceed() : blocker.reset(); } }, [blocker]); // 表单保存逻辑 const handleSave = () => { // 这里替换为实际的接口保存逻辑 setOriginalFormData(formData); alert('保存成功'); }; return ( <div> {/* 左上角Logo用Link组件,路由跳转将被自动拦截 */} <Link to="/home">网站Logo</Link> <div className="edit-form"> <h2>编辑人员信息</h2> <input type="text" value={formData.name} onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))} placeholder="姓名" /> <input type="number" value={formData.age} onChange={(e) => setFormData(prev => ({ ...prev, age: Number(e.target.value) }))} placeholder="年龄" /> <button onClick={handleSave}>保存更改</button> </div> </div> ); }; export default PersonEditPage;
关键说明
useBlocker接收一个回调函数,返回true则阻塞当前路由跳转- 当路由被阻塞时,
blocker.state变为blocked,此时弹出系统确认框,根据用户选择调用blocker.proceed()(允许跳转)或blocker.reset()(取消跳转) - 保留你原有的
beforeunload监听代码,可以同时覆盖浏览器刷新/关闭的场景
方案二:手动处理特定跳转入口(简单场景)
如果不想使用不稳定API,可以直接在Logo的点击事件中添加判断逻辑,仅处理该跳转场景:
代码示例
import { useState, useNavigate } from 'react'; const PersonEditPage = () => { const navigate = useNavigate(); const [originalName, setOriginalName] = useState('张三'); const [name, setName] = useState(originalName); const isDirty = name !== originalName; // 处理Logo点击事件 const handleLogoClick = () => { if (isDirty) { const shouldLeave = window.confirm('是否离开页面?你已做出更改,离开将丢失未保存内容。'); if (shouldLeave) { navigate('/home'); } } else { navigate('/home'); } }; return ( <div> {/* 将Logo改为可点击元素,绑定自定义点击事件 */} <div onClick={handleLogoClick} style={{ cursor: 'pointer' }}>网站Logo</div> {/* 表单内容... */} </div> ); };
注意事项
这种方式仅处理Logo点击的跳转,无法拦截浏览器前进后退、其他导航链接的跳转,适合跳转入口较少的简单场景。
内容的提问来源于stack exchange,提问作者ArthurJ
相关产品推荐
相关产品推荐

