react-router-dom BrowserRouter自定义未保存数据警告及路由拦截咨询
实现Redux应用中路由切换时的未保存表单拦截
我来帮你搞定这个路由拦截的问题!在Redux应用里监听URL变化,拦截未保存表单的路由跳转,其实有两种常用的方案,我都给你捋清楚:
一、React Router v6+:用useBlocker/usePrompt钩子(推荐)
从React Router v6开始,官方提供了专门的钩子来处理路由跳转拦截,完全可以组件级控制,还能无缝结合Redux的表单状态,非常灵活。
第一步:标记表单是否有未修改内容
先在你的表单组件里,从Redux拿表单的初始值和当前值,判断是否有未保存的修改:
import { useSelector } from 'react-redux'; const MyForm = () => { // 从Redux获取表单初始值和当前输入值 const initialFormData = useSelector(state => state.form.initialData); const currentFormData = useSelector(state => state.form.currentData); // 简单对比判断是否有未保存修改(复杂表单可以用更精细的对比逻辑) const hasUnsavedChanges = JSON.stringify(initialFormData) !== JSON.stringify(currentFormData); // ...你的表单渲染和输入逻辑 }
第二步:用useBlocker拦截路由跳转
useBlocker会在路由要跳转时触发,你可以在里面弹出自定义弹窗,然后决定是否允许跳转:
import { useBlocker, useNavigate } from 'react-router-dom'; import { useState } from 'react'; const MyForm = () => { // ...上面的hasUnsavedChanges逻辑 const navigate = useNavigate(); const [showConfirmModal, setShowConfirmModal] = useState(false); const [pendingRoute, setPendingRoute] = useState(null); // 定义拦截逻辑 const blocker = useBlocker(({ currentLocation, nextLocation }) => { // 只有当有未保存修改,且不是同一个路由跳转时才拦截 if (hasUnsavedChanges && currentLocation.pathname !== nextLocation.pathname) { setPendingRoute(nextLocation); setShowConfirmModal(true); return false; // 先阻止跳转 } return true; // 允许正常跳转 }); // 用户确认离开的处理 const handleConfirmLeave = () => { setShowConfirmModal(false); if (pendingRoute) { navigate(pendingRoute.pathname); blocker.reset(); // 重置拦截器,避免后续干扰 } }; // 用户取消离开的处理 const handleCancelLeave = () => { setShowConfirmModal(false); setPendingRoute(null); blocker.reset(); }; return ( <> {/* 你的表单内容 */} {/* 自定义确认弹窗 */} {showConfirmModal && ( <div className="custom-modal"> <p>你有未保存的修改,确定要离开吗?</p> <button onClick={handleConfirmLeave}>确定离开</button> <button onClick={handleCancelLeave}>取消</button> </div> )} </> ); };
这种方式完全不需要全局配置,弹窗样式想怎么改就怎么改,非常适合你的需求。
二、适配getUserConfirmation全局配置方案
如果你必须用getUserConfirmation,且你的代码要求它无参数、返回void,那可以结合Redux的全局状态来实现:
第一步:在Redux里维护未保存状态
先给Redux加个状态,用来标记是否有未保存的表单修改:
// action type export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES'; // action creator export const setUnsavedChanges = (hasChanges) => ({ type: SET_UNSAVED_CHANGES, payload: hasChanges }); // reducer const initialUIState = { hasUnsavedChanges: false }; export const uiReducer = (state = initialUIState, action) => { switch (action.type) { case SET_UNSAVED_CHANGES: return { ...state, hasUnsavedChanges: action.payload }; default: return state; } };
记得在表单内容变化时,调用setUnsavedChanges更新这个状态。
第二步:自定义getUserConfirmation
在配置Router的时候,传入你自定义的getUserConfirmation。官方的这个方法会接收message和callback,但你可以忽略message,只利用callback来控制是否允许跳转:
import { BrowserRouter as Router } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { useState } from 'react'; const CustomRouter = ({ children }) => { const hasUnsavedChanges = useSelector(state => state.ui.hasUnsavedChanges); const [showModal, setShowModal] = useState(false); const [resolveCallback, setResolveCallback] = useState(null); // 自定义getUserConfirmation,忽略第一个message参数 const getUserConfirmation = (_, callback) => { // 没有未保存修改,直接允许跳转 if (!hasUnsavedChanges) { callback(true); return; } // 有未保存修改,弹出自定义弹窗 setShowModal(true); setResolveCallback(callback); }; const handleConfirm = () => { setShowModal(false); resolveCallback(true); // 告诉Router允许跳转 }; const handleCancel = () => { setShowModal(false); resolveCallback(false); // 告诉Router阻止跳转 }; return ( <> <Router getUserConfirmation={getUserConfirmation}> {children} </Router> {/* 自定义确认弹窗 */} {showModal && ( <div className="custom-modal"> <p>你有未保存的修改,确定要离开吗?</p> <button onClick={handleConfirm}>确定离开</button> <button onClick={handleCancel}>取消</button> </div> )} </> ); };
之后用这个CustomRouter包裹你的整个应用就行。
额外说明
- 如果你用的是React Router v5或更早版本,官方没有
useBlocker,可以用Prompt组件,不过它默认是浏览器弹窗,要自定义的话还是得结合getUserConfirmation:import { Prompt } from 'react-router-dom'; const MyForm = () => { const hasUnsavedChanges = useSelector(state => state.ui.hasUnsavedChanges); return ( <> <Prompt when={hasUnsavedChanges} message="你有未保存的修改,确定要离开吗?" /> {/* 表单内容 */} </> ); }; - 不管用哪种方案,都要确保表单每次修改时,及时更新Redux里的
hasUnsavedChanges状态,不然拦截逻辑会失效。
内容的提问来源于stack exchange,提问作者vbullinger
相关产品推荐
相关产品推荐

