一、如果使用react-router-domV5版本时,可以使用Prompt组件实现路由跳转拦截。
1、Prompt 是 React Router 提供的一个组件,用于在导航发生之前显示一个提示,以允许或阻止用户进行导航。主要用于提示用户在离开当前页面时是否保存未提交的表单或其他重要信息。以下是 Prompt 的主要属性和功能详细介绍:
基于以上Prompt组件功能的了解,实现一个Table页面数据发生变化时,如果调转路由,提示用户是否要保存,还是取消的功能。拦截提示的效果如下:
好的,废话不多说,直接上代码:
// 路由拦截控制
const [jump,setJump] = useState(true);
{/* 路由守卫,监听页面内容是否发生变化,如果产生变化,未保存前进行路由跳转需要拦截 */}
{
localtionRef.current = location;
// 数据发生变化
if (handleDataChange()) {
setOpen(true);
return false;
}
return true;
}}
/>
{
// 拦截的Modal弹框
open && 取消,
,
,
]}
closable
destroyOnClose
/>
}
// 取消,不跳转,继续留在当前页面
const handleOnCancle = async () => {
// 关闭Modal弹框
setOpen(false);
// TODO 下面可以执行该页面的一些初始化的操作
}
// 不保存数据,跳转页面
const handleOnNotSave = async () => {
try {
// 关闭弹框
setOpen(false);
// 关闭路由守卫的拦截
setJump(false);
setTimeout(() => {
// 继续跳转页面
if (localtionRef.current) {
history.push({ ...localtionRef.current });
if (localtionRef.current?.state?.menuId) {
localtionRef.current = null;
// TODO 此处可以执行调转页面的一些需要初始化的操作
}
}, 1000);
} catch (err) {
console.error('err--:', err);
}
}
// 停留在该页面,并执行保存操作
const handleOnSave = async () => {
try {
handleOnCancle();
// TODO 下面是执行该页面的保存逻辑
} catch (err) {
console.error('err--', err);
}
};
二、如果使用react-router-domV6版本时,由于移出了对Prompt组件的支持,所以我们不能直接再使用Prompt组件进行路由拦截,但是官方提供了一个钩子函数unstable_usePrompt()可以进行路由拦截,缺点就是只支持浏览器自带的弹框提示。
unstable_usePrompt源码如下:
/**
* Wrapper around useBlocker to show a window.confirm prompt to users instead
* of building a custom UI with useBlocker.
*
* Warning: This has *a lot of rough edges* and behaves very differently (and
* very incorrectly in some cases) across browsers if user click addition
* back/forward navigations while the confirm is open. Use at your own risk.
*/
function usePrompt({
when,
message,
}: {
when: boolean | BlockerFunction;
message: string;
}) {
let blocker = useBlocker(when);
React.useEffect(() => {
if (blocker.state === "blocked") {
let proceed = window.confirm(message);
if (proceed) {
// This timeout is needed to avoid a weird "race" on POP navigations
// between the `window.history` revert navigation and the result of
// `window.confirm`
setTimeout(blocker.proceed, 0);
} else {
blocker.reset();
}
}
}, [blocker, message]);
React.useEffect(() => {
if (blocker.state === "blocked" && !when) {
blocker.reset();
}
}, [blocker, when]);
}
export { usePrompt as unstable_usePrompt };
这个很明显不符合我之前的要求,将组件react-router-dom从V5升级到V6后,我还希望支持之前的自定义的路由拦截弹框提示,因此不能直接使用该钩子函数,需要我们稍微改造一下使用:
改造代码如下:
export default function useCustomPrompt({
when,
message,
}: {
when: boolean;
message: string | (() => boolean);
}) {
// 记录要跳转的路由信息
const locationRef = useRef(null);
let blocker = useBlocker(when);
const navigate = useNavigate();
React.useEffect(() => {
try {
if (blocker.state === "blocked") {
if (blocker?.location) {
// 将有效的信息存储下来
locationRef.current = blocker.location;
}
if (typeof message === 'function') {
message() ? setTimeout(()=>{
try {
blocker.proceed && blocker.proceed();
} catch (err) {
console.error('err--',err);
}
}, 0) : blocker.reset();
}
else {
let proceed = window.confirm(message);
if (proceed) {
// This timeout is needed to avoid a weird "race" on POP navigations
// between the `window.history` revert navigation and the result of
// `window.confirm`
setTimeout(()=>{
try {
blocker.proceed && blocker.proceed();
} catch (err) {
console.error('err--',err);
}
}, 0);
} else {
blocker.reset();
}
}
} else if (blocker.state === "unblocked" && !when) { // 继续跳转之前要跳转的页面
// 执行之前存储的路径调转
newLocationRef.current && navigate(newLocationRef.current.pathname, newLocationRef.current.state);
// TODO 此处可以执行跳转页面之后的一些初始操作
}
} catch (err) {
console.error('拦截异常:', err);
}
}, [blocker, message]);
React.useEffect(() => {
try {
if (blocker.state === "blocked" && !when) {
blocker.reset && blocker.reset();
}
} catch (err) {
console.error('拦截异常:', err);
}
}, [blocker, when]);
}
然后在需要进行路由拦截的页面使用该钩子函数:
// 路由拦截控制
const [jump,setJump] = useState(true);
// 拦截提示信息弹框控制
const [open,setOpen] = useState(false);
// 路由跳转拦截
usePrompt({
when: jump,
message: () => {
// 数据发生变化
if (handleDataChange()) {
setOpen(true);
return false;
}
return true;
}
});
{
// 拦截的Modal弹框
open && 取消,
,
,
]}
closable
destroyOnClose
/>
}
// 取消,不跳转,继续留在当前页面
const handleOnCancle = async () => {
// 关闭Modal弹框
setOpen(false);
// TODO 下面可以执行该页面的一些初始化的操作
}
// 不保存数据,跳转页面
const handleOnNotSave = async () => {
try {
// 关闭弹框
setOpen(false);
// 关闭路由守卫的拦截
setJump(false);
} catch (err) {
console.error('err--:', err);
}
}
// 停留在该页面,并执行保存操作
const handleOnSave = async () => {
try {
handleOnCancle();
// TODO 下面是执行该页面的保存逻辑
} catch (err) {
console.error('err--', err);
}
};